| #include <u.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <netdb.h> |
| #include "common.h" |
| #include <auth.h> |
| #include <ndb.h> |
| |
| /* |
| * number of predefined fd's |
| */ |
| int nsysfile=3; |
| |
| static char err[Errlen]; |
| |
| /* |
| * return the date |
| */ |
| extern char * |
| thedate(void) |
| { |
| static char now[64]; |
| char *cp; |
| |
| strcpy(now, ctime(time(0))); |
| cp = strchr(now, '\n'); |
| if(cp) |
| *cp = 0; |
| return now; |
| } |
| |
| /* |
| * return the user id of the current user |
| */ |
| extern char * |
| getlog(void) |
| { |
| return getuser(); |
| } |
| |
| /* |
| * return the lock name (we use one lock per directory) |
| */ |
| static String * |
| lockname(char *path) |
| { |
| String *lp; |
| char *cp; |
| |
| /* |
| * get the name of the lock file |
| */ |
| lp = s_new(); |
| cp = strrchr(path, '/'); |
| if(cp) |
| s_nappend(lp, path, cp - path + 1); |
| s_append(lp, "L.mbox"); |
| |
| return lp; |
| } |
| |
| int |
| syscreatelocked(char *path, int mode, int perm) |
| { |
| return create(path, mode, DMEXCL|perm); |
| } |
| |
| int |
| sysopenlocked(char *path, int mode) |
| { |
| /* return open(path, OEXCL|mode);/**/ |
| return open(path, mode); /* until system call is fixed */ |
| } |
| |
| int |
| sysunlockfile(int fd) |
| { |
| return close(fd); |
| } |
| |
| /* |
| * try opening a lock file. If it doesn't exist try creating it. |
| */ |
| static int |
| openlockfile(Mlock *l) |
| { |
| int fd; |
| Dir *d; |
| Dir nd; |
| char *p; |
| |
| fd = open(s_to_c(l->name), OREAD); |
| if(fd >= 0){ |
| l->fd = fd; |
| return 0; |
| } |
| |
| d = dirstat(s_to_c(l->name)); |
| if(d == nil){ |
| /* file doesn't exist */ |
| /* try creating it */ |
| fd = create(s_to_c(l->name), OREAD, DMEXCL|0666); |
| if(fd >= 0){ |
| nulldir(&nd); |
| nd.mode = DMEXCL|0666; |
| if(dirfwstat(fd, &nd) < 0){ |
| /* if we can't chmod, don't bother */ |
| /* live without the lock but log it */ |
| syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); |
| remove(s_to_c(l->name)); |
| } |
| l->fd = fd; |
| return 0; |
| } |
| |
| /* couldn't create */ |
| /* do we have write access to the directory? */ |
| p = strrchr(s_to_c(l->name), '/'); |
| if(p != 0){ |
| *p = 0; |
| fd = access(s_to_c(l->name), 2); |
| *p = '/'; |
| if(fd < 0){ |
| /* live without the lock but log it */ |
| syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); |
| return 0; |
| } |
| } else { |
| fd = access(".", 2); |
| if(fd < 0){ |
| /* live without the lock but log it */ |
| syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name)); |
| return 0; |
| } |
| } |
| } else |
| free(d); |
| |
| return 1; /* try again later */ |
| } |
| |
| #define LSECS 5*60 |
| |
| /* |
| * Set a lock for a particular file. The lock is a file in the same directory |
| * and has L. prepended to the name of the last element of the file name. |
| */ |
| extern Mlock * |
| syslock(char *path) |
| { |
| Mlock *l; |
| int tries; |
| |
| l = mallocz(sizeof(Mlock), 1); |
| if(l == 0) |
| return nil; |
| |
| l->name = lockname(path); |
| |
| /* |
| * wait LSECS seconds for it to unlock |
| */ |
| for(tries = 0; tries < LSECS*2; tries++){ |
| switch(openlockfile(l)){ |
| case 0: |
| return l; |
| case 1: |
| sleep(500); |
| break; |
| default: |
| goto noway; |
| } |
| } |
| |
| noway: |
| s_free(l->name); |
| free(l); |
| return nil; |
| } |
| |
| /* |
| * like lock except don't wait |
| */ |
| extern Mlock * |
| trylock(char *path) |
| { |
| Mlock *l; |
| char buf[1]; |
| int fd; |
| |
| l = malloc(sizeof(Mlock)); |
| if(l == 0) |
| return 0; |
| |
| l->name = lockname(path); |
| if(openlockfile(l) != 0){ |
| s_free(l->name); |
| free(l); |
| return 0; |
| } |
| |
| /* fork process to keep lock alive */ |
| switch(l->pid = rfork(RFPROC)){ |
| default: |
| break; |
| case 0: |
| fd = l->fd; |
| for(;;){ |
| sleep(1000*60); |
| if(pread(fd, buf, 1, 0) < 0) |
| break; |
| } |
| _exits(0); |
| } |
| return l; |
| } |
| |
| extern void |
| syslockrefresh(Mlock *l) |
| { |
| char buf[1]; |
| |
| pread(l->fd, buf, 1, 0); |
| } |
| |
| extern void |
| sysunlock(Mlock *l) |
| { |
| if(l == 0) |
| return; |
| if(l->name){ |
| s_free(l->name); |
| } |
| if(l->fd >= 0) |
| close(l->fd); |
| if(l->pid > 0) |
| postnote(PNPROC, l->pid, "time to die"); |
| free(l); |
| } |
| |
| /* |
| * Open a file. The modes are: |
| * |
| * l - locked |
| * a - set append permissions |
| * r - readable |
| * w - writable |
| * A - append only (doesn't exist in Bio) |
| */ |
| extern Biobuf * |
| sysopen(char *path, char *mode, ulong perm) |
| { |
| int sysperm; |
| int sysmode; |
| int fd; |
| int docreate; |
| int append; |
| int truncate; |
| Dir *d, nd; |
| Biobuf *bp; |
| |
| /* |
| * decode the request |
| */ |
| sysperm = 0; |
| sysmode = -1; |
| docreate = 0; |
| append = 0; |
| truncate = 0; |
| for(; mode && *mode; mode++) |
| switch(*mode){ |
| case 'A': |
| sysmode = OWRITE; |
| append = 1; |
| break; |
| case 'c': |
| docreate = 1; |
| break; |
| case 'l': |
| sysperm |= DMEXCL; |
| sysmode |= OLOCK; |
| break; |
| case 'a': |
| sysperm |= DMAPPEND; |
| break; |
| case 'w': |
| if(sysmode == -1) |
| sysmode = OWRITE; |
| else |
| sysmode = ORDWR; |
| break; |
| case 'r': |
| if(sysmode == -1) |
| sysmode = OREAD; |
| else |
| sysmode = ORDWR; |
| break; |
| case 't': |
| truncate = 1; |
| break; |
| default: |
| break; |
| } |
| switch(sysmode){ |
| case OREAD: |
| case OWRITE: |
| case ORDWR: |
| break; |
| default: |
| if(sysperm&DMAPPEND) |
| sysmode = OWRITE; |
| else |
| sysmode = OREAD; |
| break; |
| } |
| |
| /* |
| * create file if we need to |
| */ |
| if(truncate) |
| sysmode |= OTRUNC; |
| fd = open(path, sysmode); |
| if(fd < 0){ |
| d = dirstat(path); |
| if(d == nil){ |
| if(docreate == 0) |
| return 0; |
| |
| fd = create(path, sysmode, sysperm|perm); |
| if(fd < 0) |
| return 0; |
| nulldir(&nd); |
| nd.mode = sysperm|perm; |
| dirfwstat(fd, &nd); |
| } else { |
| free(d); |
| return 0; |
| } |
| } |
| |
| bp = (Biobuf*)malloc(sizeof(Biobuf)); |
| if(bp == 0){ |
| close(fd); |
| return 0; |
| } |
| memset(bp, 0, sizeof(Biobuf)); |
| Binit(bp, fd, sysmode&~OTRUNC); |
| |
| if(append) |
| Bseek(bp, 0, 2); |
| return bp; |
| } |
| |
| /* |
| * close the file, etc. |
| */ |
| int |
| sysclose(Biobuf *bp) |
| { |
| int rv; |
| |
| rv = Bterm(bp); |
| close(Bfildes(bp)); |
| free(bp); |
| return rv; |
| } |
| |
| /* |
| * create a file |
| */ |
| int |
| syscreate(char *file, int mode, ulong perm) |
| { |
| return create(file, mode, perm); |
| } |
| |
| /* |
| * make a directory |
| */ |
| int |
| sysmkdir(char *file, ulong perm) |
| { |
| int fd; |
| |
| if((fd = create(file, OREAD, DMDIR|perm)) < 0) |
| return -1; |
| close(fd); |
| return 0; |
| } |
| |
| /* |
| * change the group of a file |
| */ |
| int |
| syschgrp(char *file, char *group) |
| { |
| Dir nd; |
| |
| if(group == 0) |
| return -1; |
| nulldir(&nd); |
| nd.gid = group; |
| return dirwstat(file, &nd); |
| } |
| |
| extern int |
| sysdirreadall(int fd, Dir **d) |
| { |
| return dirreadall(fd, d); |
| } |
| |
| /* |
| * read in the system name |
| */ |
| static char *unix_hostname_read(void); |
| extern char * |
| sysname_read(void) |
| { |
| static char name[128]; |
| char *cp; |
| |
| cp = getenv("site"); |
| if(cp == 0 || *cp == 0) |
| cp = alt_sysname_read(); |
| if(cp == 0 || *cp == 0) |
| cp = "kremvax"; |
| strecpy(name, name+sizeof name, cp); |
| return name; |
| } |
| extern char * |
| alt_sysname_read(void) |
| { |
| char *cp; |
| static char name[128]; |
| |
| cp = getenv("sysname"); |
| if(cp == 0 || *cp == 0) |
| cp = unix_hostname_read(); |
| if(cp == 0 || *cp == 0) |
| return 0; |
| strecpy(name, name+sizeof name, cp); |
| return name; |
| } |
| static char * |
| unix_hostname_read(void) |
| { |
| static char hostname[256]; |
| |
| if(gethostname(hostname, sizeof hostname) < 0) |
| return nil; |
| return hostname; |
| } |
| |
| /* |
| * get all names |
| */ |
| extern char** |
| sysnames_read(void) |
| { |
| static char **namev; |
| struct hostent *h; |
| char **p, **a; |
| |
| if(namev) |
| return namev; |
| |
| h = gethostbyname(alt_sysname_read()); |
| if(h == nil) |
| return 0; |
| |
| for(p=h->h_aliases; *p; p++) |
| ; |
| |
| namev = malloc((2+p-h->h_aliases)*sizeof namev[0]); |
| if(namev == 0) |
| return 0; |
| |
| a = namev; |
| *a++ = strdup(h->h_name); |
| for(p=h->h_aliases; *p; p++) |
| *a++ = strdup(*p); |
| *a = 0; |
| |
| return namev; |
| } |
| |
| /* |
| * read in the domain name. |
| * chop off beginning pieces until we find one with an mx record. |
| */ |
| extern char * |
| domainname_read(void) |
| { |
| char **namev, *p; |
| Ndbtuple *t; |
| |
| for(namev = sysnames_read(); namev && *namev; namev++){ |
| if(strchr(*namev, '.')){ |
| for(p=*namev-1; p && *++p; p=strchr(p, '.')){ |
| if((t = dnsquery(nil, p, "mx")) != nil){ |
| ndbfree(t); |
| return p; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * return true if the last error message meant file |
| * did not exist. |
| */ |
| extern int |
| e_nonexistent(void) |
| { |
| rerrstr(err, sizeof(err)); |
| return strcmp(err, "file does not exist") == 0; |
| } |
| |
| /* |
| * return true if the last error message meant file |
| * was locked. |
| */ |
| extern int |
| e_locked(void) |
| { |
| rerrstr(err, sizeof(err)); |
| return strcmp(err, "open/create -- file is locked") == 0; |
| } |
| |
| /* |
| * return the length of a file |
| */ |
| extern long |
| sysfilelen(Biobuf *fp) |
| { |
| Dir *d; |
| long rv; |
| |
| d = dirfstat(Bfildes(fp)); |
| if(d == nil) |
| return -1; |
| rv = d->length; |
| free(d); |
| return rv; |
| } |
| |
| /* |
| * remove a file |
| */ |
| extern int |
| sysremove(char *path) |
| { |
| return remove(path); |
| } |
| |
| /* |
| * rename a file, fails unless both are in the same directory |
| */ |
| extern int |
| sysrename(char *old, char *new) |
| { |
| Dir d; |
| char *obase; |
| char *nbase; |
| |
| obase = strrchr(old, '/'); |
| nbase = strrchr(new, '/'); |
| if(obase){ |
| if(nbase == 0) |
| return -1; |
| if(strncmp(old, new, obase-old) != 0) |
| return -1; |
| nbase++; |
| } else { |
| if(nbase) |
| return -1; |
| nbase = new; |
| } |
| nulldir(&d); |
| d.name = nbase; |
| return dirwstat(old, &d); |
| } |
| |
| /* |
| * see if a file exists |
| */ |
| extern int |
| sysexist(char *file) |
| { |
| Dir *d; |
| |
| d = dirstat(file); |
| if(d == nil) |
| return 0; |
| free(d); |
| return 1; |
| } |
| |
| /* |
| * return nonzero if file is a directory |
| */ |
| extern int |
| sysisdir(char *file) |
| { |
| Dir *d; |
| int rv; |
| |
| d = dirstat(file); |
| if(d == nil) |
| return 0; |
| rv = d->mode & DMDIR; |
| free(d); |
| return rv; |
| } |
| |
| /* |
| * kill a process |
| */ |
| extern int |
| syskill(int pid) |
| { |
| return postnote(PNPROC, pid, "kill"); |
| } |
| |
| /* |
| * kill a process group |
| */ |
| extern int |
| syskillpg(int pid) |
| { |
| return postnote(PNGROUP, pid, "kill"); |
| } |
| |
| extern int |
| sysdetach(void) |
| { |
| if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) { |
| werrstr("rfork failed"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * catch a write on a closed pipe |
| */ |
| static int *closedflag; |
| static int |
| catchpipe(void *a, char *msg) |
| { |
| static char *foo = "sys: write on closed pipe"; |
| |
| USED(a); |
| if(strncmp(msg, foo, strlen(foo)) == 0){ |
| if(closedflag) |
| *closedflag = 1; |
| return 1; |
| } |
| return 0; |
| } |
| void |
| pipesig(int *flagp) |
| { |
| closedflag = flagp; |
| atnotify(catchpipe, 1); |
| } |
| void |
| pipesigoff(void) |
| { |
| atnotify(catchpipe, 0); |
| } |
| |
| extern int |
| holdon(void) |
| { |
| /* XXX talk to 9term? */ |
| return -1; |
| } |
| |
| extern int |
| sysopentty(void) |
| { |
| return open("/dev/tty", ORDWR); |
| } |
| |
| extern void |
| holdoff(int fd) |
| { |
| write(fd, "holdoff", 7); |
| close(fd); |
| } |
| |
| extern int |
| sysfiles(void) |
| { |
| return 128; |
| } |
| |
| /* |
| * expand a path relative to the user's mailbox directory |
| * |
| * if the path starts with / or ./, don't change it |
| * |
| */ |
| extern String * |
| mboxpath(char *path, char *user, String *to, int dot) |
| { |
| char *dir; |
| String *s; |
| |
| if (dot || *path=='/' || strncmp(path, "./", 2) == 0 |
| || strncmp(path, "../", 3) == 0) { |
| to = s_append(to, path); |
| } else { |
| if ((dir = homedir(user)) != nil) { |
| s = s_copy(dir); |
| s_append(s, "/mail/"); |
| if(access(s_to_c(s), AEXIST) >= 0){ |
| to = s_append(to, s_to_c(s)); |
| s_free(s); |
| to = s_append(to, path); |
| return to; |
| } |
| s_free(s); |
| } |
| to = s_append(to, MAILROOT); |
| to = s_append(to, "/box/"); |
| to = s_append(to, user); |
| to = s_append(to, "/"); |
| to = s_append(to, path); |
| } |
| return to; |
| } |
| |
| extern String * |
| mboxname(char *user, String *to) |
| { |
| return mboxpath("mbox", user, to, 0); |
| } |
| |
| extern String * |
| deadletter(String *to) /* pass in sender??? */ |
| { |
| char *cp; |
| |
| cp = getlog(); |
| if(cp == 0) |
| return 0; |
| return mboxpath("dead.letter", cp, to, 0); |
| } |
| |
| String * |
| readlock(String *file) |
| { |
| char *cp; |
| |
| cp = getlog(); |
| if(cp == 0) |
| return 0; |
| return mboxpath("reading", cp, file, 0); |
| } |
| |
| String * |
| username(String *from) |
| { |
| String* s; |
| struct passwd* pw; |
| |
| setpwent(); |
| while((pw = getpwent()) != nil){ |
| if(strcmp(s_to_c(from), pw->pw_name) == 0){ |
| s = s_new(); |
| s_append(s, "\""); |
| s_append(s, pw->pw_gecos); |
| s_append(s, "\""); |
| return s; |
| } |
| } |
| return nil; |
| } |
| |
| char * |
| homedir(char *user) |
| { |
| static char buf[1024]; |
| struct passwd* pw; |
| |
| setpwent(); |
| while((pw = getpwent()) != nil) |
| if(strcmp(user, pw->pw_name) == 0){ |
| strecpy(buf, buf+sizeof buf, pw->pw_dir); |
| return buf; |
| } |
| return nil; |
| } |
| |
| char * |
| remoteaddr(int fd, char *dir) |
| { |
| char *raddr; |
| NetConnInfo *nci; |
| |
| if((nci = getnetconninfo(dir, fd)) == nil) |
| return nil; |
| raddr = strdup(nci->raddr); |
| freenetconninfo(nci); |
| return raddr; |
| } |
| |
| /* create a file and */ |
| /* 1) ensure the modes we asked for */ |
| /* 2) make gid == uid */ |
| static int |
| docreate(char *file, int perm) |
| { |
| int fd; |
| Dir ndir; |
| Dir *d; |
| |
| /* create the mbox */ |
| fd = create(file, OREAD, perm); |
| if(fd < 0){ |
| fprint(2, "couldn't create %s\n", file); |
| return -1; |
| } |
| d = dirfstat(fd); |
| if(d == nil){ |
| fprint(2, "couldn't stat %s\n", file); |
| return -1; |
| } |
| nulldir(&ndir); |
| ndir.mode = perm; |
| ndir.gid = d->uid; |
| if(dirfwstat(fd, &ndir) < 0) |
| fprint(2, "couldn't chmod %s: %r\n", file); |
| close(fd); |
| return 0; |
| } |
| |
| /* create a mailbox */ |
| int |
| creatembox(char *user, char *folder) |
| { |
| char *p; |
| String *mailfile; |
| char buf[512]; |
| Mlock *ml; |
| |
| mailfile = s_new(); |
| if(folder == 0) |
| mboxname(user, mailfile); |
| else { |
| snprint(buf, sizeof(buf), "%s/mbox", folder); |
| mboxpath(buf, user, mailfile, 0); |
| } |
| |
| /* don't destroy existing mailbox */ |
| if(access(s_to_c(mailfile), 0) == 0){ |
| fprint(2, "mailbox already exists\n"); |
| return -1; |
| } |
| fprint(2, "creating new mbox: %s\n", s_to_c(mailfile)); |
| |
| /* make sure preceding levels exist */ |
| for(p = s_to_c(mailfile); p; p++) { |
| if(*p == '/') /* skip leading or consecutive slashes */ |
| continue; |
| p = strchr(p, '/'); |
| if(p == 0) |
| break; |
| *p = 0; |
| if(access(s_to_c(mailfile), 0) != 0){ |
| if(docreate(s_to_c(mailfile), DMDIR|0711) < 0) |
| return -1; |
| } |
| *p = '/'; |
| } |
| |
| /* create the mbox */ |
| if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0) |
| return -1; |
| |
| /* |
| * create the lock file if it doesn't exist |
| */ |
| ml = trylock(s_to_c(mailfile)); |
| if(ml != nil) |
| sysunlock(ml); |
| |
| return 0; |
| } |