1 /*- 2 * Copyright (c) 1990 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)mail.local.c 5.8 (Berkeley) 06/22/92"; 16 #endif /* not lint */ 17 18 #include <sys/param.h> 19 #include <sys/stat.h> 20 #include <sys/socket.h> 21 #include <netinet/in.h> 22 #include <syslog.h> 23 #include <fcntl.h> 24 #include <netdb.h> 25 #include <pwd.h> 26 #include <time.h> 27 #include <unistd.h> 28 #include <errno.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include "pathnames.h" 33 34 #define FATAL 1 35 #define NOTFATAL 0 36 37 int deliver __P((int, char *)); 38 void err __P((int, const char *, ...)); 39 void notifybiff __P((char *)); 40 int store __P((char *)); 41 void usage __P((void)); 42 43 main(argc, argv) 44 int argc; 45 char **argv; 46 { 47 struct passwd *pw; 48 int ch, fd, eval; 49 uid_t uid; 50 char *from; 51 52 openlog("mail.local", LOG_PERROR, LOG_MAIL); 53 54 from = NULL; 55 while ((ch = getopt(argc, argv, "df:r:")) != EOF) 56 switch(ch) { 57 case 'd': /* backward compatible */ 58 break; 59 case 'f': 60 case 'r': /* backward compatible */ 61 if (from) 62 err(FATAL, "multiple -f options"); 63 from = optarg; 64 break; 65 case '?': 66 default: 67 usage(); 68 } 69 argc -= optind; 70 argv += optind; 71 72 if (!*argv) 73 usage(); 74 75 /* 76 * If from not specified, use the name from getlogin() if the 77 * uid matches, otherwise, use the name from the password file 78 * corresponding to the uid. 79 */ 80 uid = getuid(); 81 if (!from && (!(from = getlogin()) || 82 !(pw = getpwnam(from)) || pw->pw_uid != uid)) 83 from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 84 85 fd = store(from); 86 for (eval = 0; *argv; ++argv) 87 eval |= deliver(fd, *argv); 88 exit(eval); 89 } 90 91 store(from) 92 char *from; 93 { 94 FILE *fp; 95 time_t tval; 96 int fd, eline; 97 char *tn, line[2048]; 98 99 tn = strdup(_PATH_LOCTMP); 100 if ((fd = mkstemp(tn)) == -1 || !(fp = fdopen(fd, "w+"))) 101 err(FATAL, "unable to open temporary file"); 102 (void)unlink(tn); 103 free(tn); 104 105 (void)time(&tval); 106 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 107 108 line[0] = '\0'; 109 for (eline = 1; fgets(line, sizeof(line), stdin);) { 110 if (line[0] == '\n') 111 eline = 1; 112 else { 113 if (eline && line[0] == 'F' && !bcmp(line, "From ", 5)) 114 (void)putc('>', fp); 115 eline = 0; 116 } 117 (void)fprintf(fp, "%s", line); 118 if (ferror(fp)) 119 break; 120 } 121 122 /* If message not newline terminated, need an extra. */ 123 if (!index(line, '\n')) 124 (void)putc('\n', fp); 125 /* Output a newline; note, empty messages are allowed. */ 126 (void)putc('\n', fp); 127 128 (void)fflush(fp); 129 if (ferror(fp)) 130 err(FATAL, "temporary file write error"); 131 return(fd); 132 } 133 134 deliver(fd, name) 135 int fd; 136 char *name; 137 { 138 struct stat sb; 139 struct passwd *pw; 140 int created, mbfd, nr, nw, off, rval; 141 char biffmsg[100], buf[8*1024], path[MAXPATHLEN]; 142 off_t curoff; 143 144 /* 145 * Disallow delivery to unknown names -- special mailboxes can be 146 * handled in the sendmail aliases file. 147 */ 148 if (!(pw = getpwnam(name))) { 149 err(NOTFATAL, "unknown name: %s", name); 150 return(1); 151 } 152 153 (void)sprintf(path, "%s/%s", _PATH_MAILDIR, name); 154 155 if (!(created = lstat(path, &sb)) && 156 (sb.st_nlink != 1 || S_ISLNK(sb.st_mode))) { 157 err(NOTFATAL, "%s: linked file", path); 158 return(1); 159 } 160 161 /* 162 * There's a race here -- two processes think they both created 163 * the file. This means the file cannot be unlinked. 164 */ 165 if ((mbfd = 166 open(path, O_APPEND|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) { 167 err(NOTFATAL, "%s: %s", path, strerror(errno)); 168 return(1); 169 } 170 171 rval = 0; 172 /* XXX: Open should allow flock'ing the file; see 4.4BSD. */ 173 if (flock(mbfd, LOCK_EX)) { 174 err(NOTFATAL, "%s: %s", path, strerror(errno)); 175 rval = 1; 176 goto bad; 177 } 178 179 curoff = lseek(mbfd, (off_t)0, SEEK_END); 180 (void)sprintf(biffmsg, "%s@%qd\n", name, curoff); 181 if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) { 182 err(FATAL, "temporary file: %s", strerror(errno)); 183 rval = 1; 184 goto bad; 185 } 186 187 while ((nr = read(fd, buf, sizeof(buf))) > 0) 188 for (off = 0; off < nr; nr -= nw, off += nw) 189 if ((nw = write(mbfd, buf + off, nr)) < 0) { 190 err(NOTFATAL, "%s: %s", path, strerror(errno)); 191 goto trunc; 192 } 193 if (nr < 0) { 194 err(FATAL, "temporary file: %s", strerror(errno)); 195 trunc: (void)ftruncate(mbfd, curoff); 196 rval = 1; 197 } 198 199 /* 200 * Set the owner and group. Historically, binmail repeated this at 201 * each mail delivery. We no longer do this, assuming that if the 202 * ownership or permissions were changed there was a reason for doing 203 * so. 204 */ 205 bad: if (created) 206 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 207 208 (void)fsync(mbfd); /* Don't wait for update. */ 209 (void)close(mbfd); /* Implicit unlock. */ 210 211 if (!rval) 212 notifybiff(biffmsg); 213 return(rval); 214 } 215 216 void 217 notifybiff(msg) 218 char *msg; 219 { 220 static struct sockaddr_in addr; 221 static int f = -1; 222 struct hostent *hp; 223 struct servent *sp; 224 int len; 225 226 if (!addr.sin_family) { 227 /* Be silent if biff service not available. */ 228 if (!(sp = getservbyname("biff", "udp"))) 229 return; 230 if (!(hp = gethostbyname("localhost"))) { 231 err(NOTFATAL, "localhost: %s", strerror(errno)); 232 return; 233 } 234 addr.sin_family = hp->h_addrtype; 235 bcopy(hp->h_addr, &addr.sin_addr, hp->h_length); 236 addr.sin_port = sp->s_port; 237 } 238 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 239 err(NOTFATAL, "socket: %s", strerror(errno)); 240 return; 241 } 242 len = strlen(msg) + 1; 243 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 244 != len) 245 err(NOTFATAL, "sendto biff: %s", strerror(errno)); 246 } 247 248 void 249 usage() 250 { 251 err(FATAL, "usage: mail.local [-f from] user ..."); 252 } 253 254 #if __STDC__ 255 #include <stdarg.h> 256 #else 257 #include <varargs.h> 258 #endif 259 260 void 261 #if __STDC__ 262 err(int isfatal, const char *fmt, ...) 263 #else 264 err(isfatal, fmt, va_alist) 265 int isfatal; 266 char *fmt; 267 va_dcl 268 #endif 269 { 270 va_list ap; 271 #if __STDC__ 272 va_start(ap, fmt); 273 #else 274 va_start(ap); 275 #endif 276 vsyslog(LOG_ERR, fmt, ap); 277 va_end(ap); 278 if (isfatal) 279 exit(1); 280 } 281