#ifdef sccs static char sccsid[] = "@(#)pcnfsd.c 1.4"; #endif /* * Copyright (c) 1986 by Sun Microsystems, Inc. */ /* * pcnfsd.c * * pcnfsd is intended to remedy the lack of certain critical generic network * services by providing an simple, customizable set of RPC-based * mechanisms. For this reason, Sun Microsystems Inc. is distributing it * in source form as part of the PC-NFS release. * * Background: The first NFS networks were composed of systems running * derivatives of the 4.2BSD release of Unix (Sun's, VAXes, Goulds and * Pyramids). The immediate utility of the resulting networks was derived * not only from NFS but also from the availability of a number of TCP/IP * based network services derived from 4.2BSD. Furthermore the thorny * question of network-wide user authentication, while remaining a * security hole, was solved at least in terms of a convenient usage model * by the Yellow Pages distributed data base facility, which allows * multiple Unix systems to refer to common password and group files. * * The PC-NFS Dilemma: When Sun Microsystems Inc. ported NFS to PC's, two * things became apparent. First, the memory constraints of the typical PC * meant that it would be impossible to incorporate the pervasive TCP/IP * based service suite in a resident fashion. Indeed it was not at all * clear that the 4.2BSD services would prove sufficient: with the advent * of Unix System V and (experimental) VAX-VMS NFS implementations, we had * to consider the existence of networks with no BSD-derived Unix hosts. * The two key types of functionality we needed to provide were remote * login and print spooling. The second critical issue was that of user * authentication. Traditional time-sharing systems such as Unix and VMS * have well- established user authentication mechanisms based upon user * id's and passwords: by defining appropriate mappings, these could * suffice for network-wide authentication provided that appropriate * administrative procedures were enforced. The PC, however, is typically * a single-user system, and the standard DOS operating environment * provides no user authentication mechanisms. While this is acceptable * within a single PC, it causes problems when attempting to connect to a * heterogeneous network of systems in which access control, file space * allocation, and print job accounting and routing may all be based upon * a user's identity. The initial (and default) approach is to use the * pseudo-identity 'nobody' defined as part of NFS to handle problems such * as this. However, taking ease of use into consideration, it became * necessary to provide a mechanism for establishing a user's identity. * * Initially we felt that we needed to implement two types of functionality: * user authentication and print spooling. (Remote login is addressed by * the Telnet module.) Since no network services were defined within the * NFS architecture to support these, it was decided to implement them in * a fairly portable fashion using Sun's Remote Procedure Call protocol. * Since these mechanisms will need to be re-implemented ion a variety of * software environments, we have tried to define a very general model. * * Authentication: NFS adopts the Unix model of using a pair of integers * (uid, gid) to define a user's identity. This happens to map tolerably * well onto the VMS system. 'pcnfsd' implements a Remote Procedure which * is required to map a username and password into a (uid, gid) pair. * Since we cannot predict what mapping is to be performed, and since we * do not wish to pass clear-text passwords over the net, both the * username and the password are mildly scrambled using a simple XOR * operation. The intent is not to be secure (the present NFS architecture * is inherently insecure) but to defeat "browsers". * * The authentication RPC will be invoked when the user enters the PC-NFS * command: * * NET NAME user [password|*] * * * Printing: The availability of NFS file operations simplifies the print * spooling mechanisms. There are two services which 'pcnfsd' has to * provide: * pr_init: given the name of the client system, return the * name of a directory which is exported via NFS and in which the client * may create spool files. * pr_start: given a file name, a user name, the printer name, the client * system name and an option string, initiate printing of the file * on the named printer. The file name is relative to the directory * returned by pr_init. pr_start is to be "idempotent": a request to print * a file which is already being printed has no effect. * * Intent: The first versions of these procedures are implementations for Sun * 2.0/3.0 software, which will also run on VAX 4.2BSD systems. The intent * is to build up a set of implementations for different architectures * (Unix System V, VMS, etc.). Users are encouraged to submit their own * variations for redistribution. If you need a particular variation which * you don't see here, either code it yourself (and, hopefully, send it to * us at Sun) or contact your Customer Support representative. */ #include #include #ifdef OS2 #undef min #include #include #include #include #include #include #include #define mkdir(p, m) mkdir(p) #define INCL_DOS #include extern char *crypt(char *, char*); #else #include #endif #ifdef SHADOWPWD #include #include #else #include #endif #include #include #include /* #define DEBUG 1 */ #ifdef DEBUG int buggit = 0; #endif #ifdef OS2 # define LP_SPOOL "C:\\spool\\pcnfs" # define LP_BIN "print.com" # define DIRSEP "\\" #else # define LP_SPOOL "/usr/spool/lp" # define LP_BIN "/usr/ucb/lpr" # define DIRSEP "/" #endif /* * *************** RPC parameters ******************** */ #define PCNFSDPROG (long)150001 #define PCNFSDVERS (long)1 #define PCNFSD_AUTH (long)1 #define PCNFSD_PR_INIT (long)2 #define PCNFSD_PR_START (long)3 /* * ************* Other #define's ********************** */ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif #define zchar 0x5b /* * *********** XDR structures, etc. ******************** */ enum arstat { AUTH_RES_OK, AUTH_RES_FAKE, AUTH_RES_FAIL }; enum pirstat { PI_RES_OK, PI_RES_NO_SUCH_PRINTER, PI_RES_FAIL }; enum psrstat { PS_RES_OK, PS_RES_ALREADY, PS_RES_NULL, PS_RES_NO_FILE, PS_RES_FAIL }; struct auth_args { char *aa_ident; char *aa_password; }; struct auth_results { enum arstat ar_stat; long ar_uid; long ar_gid; }; struct pr_init_args { char *pia_client; char *pia_printername; }; struct pr_init_results { enum pirstat pir_stat; char *pir_spooldir; }; struct pr_start_args { char *psa_client; char *psa_printername; char *psa_username; char *psa_filename; /* within the spooldir */ char *psa_options; }; struct pr_start_results { enum psrstat psr_stat; }; /* * ****************** Misc. ************************ */ char *authproc(); char *pr_start(); char *pr_init(); struct stat statbuf; char pathname[MAXPATHLEN]; char new_pathname[MAXPATHLEN]; char spoolname[MAXPATHLEN]; /* * ************** Support procedures *********************** */ scramble(s1, s2) char *s1; char *s2; { while (*s1) { *s2++ = (*s1 ^ zchar) & 0x7f; s1++; } *s2 = 0; } free_child() { int pid; int pstatus; pid = wait(&pstatus); /* clear exit of child process */ #ifdef DEBUG if (buggit || pstatus) fprintf(stderr, "FREE_CHILD: process #%d exited with status 0X%x\r\n", pid, pstatus); #endif return; } /* * *************** XDR procedures ***************** */ bool_t xdr_auth_args(xdrs, aap) XDR *xdrs; struct auth_args *aap; { return (xdr_string(xdrs, &aap->aa_ident, 32) && xdr_string(xdrs, &aap->aa_password, 64)); } bool_t xdr_auth_results(xdrs, arp) XDR *xdrs; struct auth_results *arp; { return (xdr_enum(xdrs, (int *) &arp->ar_stat) && xdr_long(xdrs, &arp->ar_uid) && xdr_long(xdrs, &arp->ar_gid)); } bool_t xdr_pr_init_args(xdrs, aap) XDR *xdrs; struct pr_init_args *aap; { return (xdr_string(xdrs, &aap->pia_client, 64) && xdr_string(xdrs, &aap->pia_printername, 64)); } bool_t xdr_pr_init_results(xdrs, arp) XDR *xdrs; struct pr_init_results *arp; { return (xdr_enum(xdrs, (int *) &arp->pir_stat) && xdr_string(xdrs, &arp->pir_spooldir, 255)); } bool_t xdr_pr_start_args(xdrs, aap) XDR *xdrs; struct pr_start_args *aap; { return (xdr_string(xdrs, &aap->psa_client, 64) && xdr_string(xdrs, &aap->psa_printername, 64) && xdr_string(xdrs, &aap->psa_username, 64) && xdr_string(xdrs, &aap->psa_filename, 64) && xdr_string(xdrs, &aap->psa_options, 64)); } bool_t xdr_pr_start_results(xdrs, arp) XDR *xdrs; struct pr_start_results *arp; { return (xdr_enum(xdrs, (int *) &arp->psr_stat)); } /* * ********************** main ********************* */ main(argc, argv) int argc; char **argv; { int f1, f2, f3; extern xdr_string_array(); #ifdef OS2 if (argc < 2) { strcpy(spoolname, LP_SPOOL); spoolname[0] = 'A' - 1 + _getdrive(); } #else if (fork() == 0) { if (argc < 2) strcpy(spoolname, LP_SPOOL); #endif else strcpy(spoolname, argv[1]); #ifdef DEBUG if (argc > 2) buggit++; #endif if (stat(spoolname, &statbuf) || !(statbuf.st_mode & S_IFDIR)) { fprintf(stderr, "pcnfsd: invalid spool directory %s\r\n", spoolname); exit(1); } /* Comment out for now if ((f1 = open("/dev/null", O_RDONLY)) == -1) { fprintf(stderr, "pcnfsd: couldn't open /dev/null\r\n"); exit(1); } if ((f2 = open("/dev/console", O_WRONLY)) == -1) { fprintf(stderr, "pcnfsd: couldn't open /dev/console\r\n"); exit(1); } if ((f3 = open("/dev/console", O_WRONLY)) == -1) { fprintf(stderr, "pcnfsd: couldn't open /dev/console\r\n"); exit(1); } dup2(f1, 0); dup2(f2, 1); dup2(f3, 2); end of commented out stuff */ registerrpc(PCNFSDPROG, PCNFSDVERS, PCNFSD_AUTH, authproc, xdr_auth_args, xdr_auth_results); registerrpc(PCNFSDPROG, PCNFSDVERS, PCNFSD_PR_INIT, pr_init, xdr_pr_init_args, xdr_pr_init_results); registerrpc(PCNFSDPROG, PCNFSDVERS, PCNFSD_PR_START, pr_start, xdr_pr_start_args, xdr_pr_start_results); svc_run(); fprintf(stderr, "pcnfsd: error: svc_run returned\r\n"); exit(1); #ifndef OS2 } #endif } /* * ******************* RPC procedures ************** */ char * authproc(a) struct auth_args *a; { static struct auth_results r; char username[32]; char password[64]; int c1, c2; struct passwd *p; #ifdef SHADOWPWD struct spwd *spwd; #endif r.ar_stat = AUTH_RES_FAIL; /* assume failure */ scramble(a->aa_ident, username); scramble(a->aa_password, password); #ifdef DEBUG if (buggit) fprintf(stderr, "AUTHPROC username=%s\r\n", username); #endif p = getpwnam(username); if (p == NULL) return ((char *) &r); #ifdef SHADOWPWD if (!(spwd = getspnam(username))) return ((char *) &r); else p->pw_passwd = spwd->sp_pwdp; if (p->pw_name && p->pw_passwd[0] == '@') { if (pw_auth(p->pw_passwd+1, username, PW_LOGIN)) return ((char *) &r); } else { if (!valid(password, p)) return ((char *) &r); } #else c1 = strlen(password); c2 = strlen(p->pw_passwd); if ((c1 && !c2) || (c2 && !c1) || (strcmp(p->pw_passwd, crypt(password, p->pw_passwd)))) { return ((char *) &r); } #endif r.ar_stat = AUTH_RES_OK; r.ar_uid = p->pw_uid; r.ar_gid = p->pw_gid; return ((char *) &r); } char * pr_init(pi_arg) struct pr_init_args *pi_arg; { int dir_mode = 0777; static struct pr_init_results pi_res; /* get pathname of current directory and return to client */ strcpy(pathname, spoolname); /* first the spool area */ strcat(pathname, DIRSEP); /* append a slash */ strcat(pathname, pi_arg->pia_client); /* now the host name */ mkdir(pathname, dir_mode); /* ignore the return code */ if (stat(pathname, &statbuf) || !(statbuf.st_mode & S_IFDIR)) { fprintf(stderr, "pcnfsd: unable to create spool directory %s\r\n", pathname); pathname[0] = 0;/* null to tell client bad vibes */ pi_res.pir_stat = PI_RES_FAIL; } else { pi_res.pir_stat = PI_RES_OK; } pi_res.pir_spooldir = &pathname[0]; chmod(pathname, dir_mode); #ifdef DEBUG if (buggit) fprintf(stderr, "PR_INIT pathname=%s\r\n", pathname); #endif return ((char *) &pi_res); } char * pr_start(ps_arg) struct pr_start_args *ps_arg; { static struct pr_start_results ps_res; int pid; int free_child(); char printer_opt[64]; char username_opt[64]; char clientname_opt[64]; struct passwd *p; long rnum; char snum[20]; int z; #ifdef OS2 strcpy(printer_opt, "/D:"); /* Strangely, sometimes there are garbage characters after the eighth * character. And since there can at most be eight, we truncate here. */ if (strlen(ps_arg->psa_filename) > 8) ps_arg->psa_filename[8] = 0; #else strcpy(printer_opt, "-P"); #endif strcpy(username_opt, "-J"); strcpy(clientname_opt, "-C"); #ifdef SIGCHLD signal(SIGCHLD, free_child); /* when child terminates it sends */ #endif /* a signal which we must get */ strcpy(pathname, spoolname); /* build filename */ strcat(pathname, DIRSEP); strcat(pathname, ps_arg->psa_client); /* /spool/host */ strcat(pathname, DIRSEP); /* /spool/host/ */ strcat(pathname, ps_arg->psa_filename); /* /spool/host/file */ #ifdef DEBUG if (buggit) { fprintf(stderr, "PR_START pathname=%s\r\n", pathname); fprintf(stderr, "PR_START username= %s\r\n", ps_arg->psa_username); fprintf(stderr, "PR_START client= %s\r\n", ps_arg->psa_client); } #endif strcat(printer_opt, ps_arg->psa_printername); /* make it (e.g.) -Plw */ strcat(username_opt, ps_arg->psa_username); /* make it (e.g.) -Jbilly */ strcat(clientname_opt, ps_arg->psa_client); /* make it (e.g.) -Cmypc */ if (stat(pathname, &statbuf)) { /* * We can't stat the file. Let's try appending '.spl' and * see if it's already in progress. */ #ifdef DEBUG if (buggit) fprintf(stderr, "...can't stat it.\r\n"); #endif strcat(pathname, ".spl"); if (stat(pathname, &statbuf)) { /* * It really doesn't exist. */ #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_NO_FILE\r\n"); #endif ps_res.psr_stat = PS_RES_NO_FILE; return ((char *) &ps_res); } /* * It is already on the way. */ #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_ALREADY\r\n"); #endif ps_res.psr_stat = PS_RES_ALREADY; return ((char *) &ps_res); } if (statbuf.st_size == 0) { /* * Null file - don't print it, just kill it. */ unlink(pathname); #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_NULL\r\n"); #endif ps_res.psr_stat = PS_RES_NULL; return ((char *) &ps_res); } /* * The file is real, has some data, and is not already going out. * We rename it by appending '.spl' and exec "lpr" to do the * actual work. */ strcpy(new_pathname, pathname); strcat(new_pathname, ".spl"); #ifdef DEBUG if (buggit) fprintf(stderr, "...renaming %s -> %s\r\n", pathname, new_pathname); #endif /* * See if the new filename exists so as not to overwrite it. */ for(z = 0; z <100; z++) { if (!stat(new_pathname, &statbuf)){ strcpy(new_pathname, pathname); /* rebuild a new name */ sprintf(snum,"%ld",random()); /* get some number */ strncat(new_pathname, snum, 3); strcat(new_pathname, ".spl"); /* new spool file */ #ifdef DEBUG if (buggit) fprintf(stderr, "...created new spl file -> %s\r\n", new_pathname); #endif } else break; } z = rename(pathname, new_pathname); #ifdef OS2 while (z && errno == EISOPEN) { /* You can't rename an open file under OS/2, */ /* it may still be opened by the nfsd server. */ DosSleep(100); /* wait a little bit */ z = rename(pathname, new_pathname); /* and retry */ } #endif if (z) { /* * CAVEAT: Microsoft changed rename for Microsoft C V3.0. * Check this if porting to Xenix. */ /* * Should never happen. */ fprintf(stderr, "pcnfsd: spool file rename (%s->%s) failed.\r\n", pathname, new_pathname); ps_res.psr_stat = PS_RES_FAIL; return ((char *) &ps_res); } #ifdef OS2 pid = spawnlp(P_WAIT, LP_BIN, LP_BIN, printer_opt, new_pathname, 0); if (pid < 0) { perror("pcnfsd: spawn print failed"); } else { unlink(new_pathname); #ifdef DEBUG if (buggit) fprintf(stderr, "...spawned child, result = %d\r\n", pid); #endif #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_OK\r\n"); #endif ps_res.psr_stat = PS_RES_OK; return ((char *) &ps_res); } #else /* !OS2 */ pid = fork(); if (pid == 0) { #ifdef DEBUG if (buggit) fprintf(stderr, "...print options =%s\r\n", ps_arg->psa_options); #endif if (ps_arg->psa_options[1] == 'd') { /* * This is a Diablo print stream. Apply the ps630 * filter with the appropriate arguments. */ #ifdef DEBUG if (buggit) fprintf(stderr, "...run_ps630 invoked\r\n"); #endif run_ps630(new_pathname, ps_arg->psa_options); } execlp(LP_BIN, "lpr", "-s", "-r", printer_opt, username_opt, clientname_opt, new_pathname, 0); perror("pcnfsd: exec lpr failed"); exit(0); /* end of child process */ } else if (pid == -1) { perror("pcnfsd: fork failed"); #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_FAIL\r\n"); #endif ps_res.psr_stat = PS_RES_FAIL; return ((char *) &ps_res); } else { #ifdef DEBUG if (buggit) fprintf(stderr, "...forked child #%d\r\n", pid); #endif #ifdef DEBUG if (buggit) fprintf(stderr, "...PR_START returns PS_RES_OK\r\n"); #endif ps_res.psr_stat = PS_RES_OK; return ((char *) &ps_res); } #endif /* OS2 */ } char * mapfont(f, i, b) char f; char i; char b; { static char fontname[64]; fontname[0] = 0; /* clear it out */ switch (f) { case 'c': strcpy(fontname, "Courier"); break; case 'h': strcpy(fontname, "Helvetica"); break; case 't': strcpy(fontname, "Times"); break; default: strcpy(fontname, "Times-Roman"); goto exit; } if (i != 'o' && b != 'b') { /* no bold or oblique */ if (f == 't') /* special case Times */ strcat(fontname, "-Roman"); goto exit; } strcat(fontname, "-"); if (b == 'b') strcat(fontname, "Bold"); if (i == 'o') /* o-blique */ strcat(fontname, f == 't' ? "Italic" : "Oblique"); exit: return (&fontname[0]); } /* * run_ps630 performs the Diablo 630 emulation filtering process. ps630 is * currently broken in the Sun release: it will not accept point size or * font changes. If your version is fixed, define the symbol * PS630_IS_FIXED and rebuild pcnfsd. */ run_ps630(file, options) char *file; char *options; { char tmpfile[256]; char commbuf[256]; int i; strcpy(tmpfile, file); strcat(tmpfile, "X"); /* intermediate file name */ #ifdef PS630_IS_FIXED sprintf(commbuf, "ps630 -s %c%c -p %s -f ", options[2], options[3], tmpfile); strcat(commbuf, mapfont(options[4], options[5], options[6])); strcat(commbuf, " -F "); strcat(commbuf, mapfont(options[7], options[8], options[9])); strcat(commbuf, " "); strcat(commbuf, file); #else /* * The pitch and font features of ps630 appear to be broken at * this time. If you think it's been fixed at your site, define * the compile-time symbol `ps630_is_fixed'. */ sprintf(commbuf, "/usr/local/bin/ps630 -p %s %s", tmpfile, file); #endif if (i = system(commbuf)) { /* * Under (un)certain conditions, ps630 may return -1 * even if it worked. Hence the commenting out of this * error report. */ /* fprintf(stderr, "\r\n\nrun_ps630 rc = %d\r\n", i) */ ; /* exit(1); */ } if (rename(tmpfile, file)) { perror("run_ps630: rename"); exit(1); } }