1 /* 2 * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1988, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * By using this file, you agree to the terms and conditions set 8 * forth in the LICENSE file which can be found at the top level of 9 * the sendmail distribution. 10 * 11 */ 12 13 #include <sm/gen.h> 14 15 SM_IDSTR(copyright, 16 "@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\ 17 All rights reserved.\n\ 18 Copyright (c) 1988, 1993\n\ 19 The Regents of the University of California. All rights reserved.\n") 20 21 SM_IDSTR(id, "@(#)$Id: rmail.c,v 1.1.1.8 2002/02/17 21:56:45 gshapiro Exp $") 22 23 /* $FreeBSD$ */ 24 25 /* 26 * RMAIL -- UUCP mail server. 27 * 28 * This program reads the >From ... remote from ... lines that UUCP is so 29 * fond of and turns them into something reasonable. It then execs sendmail 30 * with various options built from these lines. 31 * 32 * The expected syntax is: 33 * 34 * <user> := [-a-z0-9]+ 35 * <date> := ctime format 36 * <site> := [-a-z0-9!]+ 37 * <blank line> := "^\n$" 38 * <from> := "From" <space> <user> <space> <date> 39 * [<space> "remote from" <space> <site>] 40 * <forward> := ">" <from> 41 * msg := <from> <forward>* <blank-line> <body> 42 * 43 * The output of rmail(8) compresses the <forward> lines into a single 44 * from path. 45 * 46 * The err(3) routine is included here deliberately to make this code 47 * a bit more portable. 48 */ 49 50 #include <sys/types.h> 51 #include <sys/param.h> 52 #include <sys/stat.h> 53 #include <sys/wait.h> 54 55 #include <ctype.h> 56 #include <fcntl.h> 57 #include <sm/io.h> 58 #include <stdlib.h> 59 #include <sm/string.h> 60 #include <unistd.h> 61 #ifdef EX_OK 62 # undef EX_OK /* unistd.h may have another use for this */ 63 #endif /* EX_OK */ 64 #include <sysexits.h> 65 66 #include <sm/conf.h> 67 #include <sm/errstring.h> 68 #include <sendmail/pathnames.h> 69 70 static void err __P((int, const char *, ...)); 71 static void usage __P((void)); 72 static char *xalloc __P((int)); 73 74 #define newstr(s) strcpy(xalloc(strlen(s) + 1), s) 75 76 static char * 77 xalloc(sz) 78 register int sz; 79 { 80 register char *p; 81 82 /* some systems can't handle size zero mallocs */ 83 if (sz <= 0) 84 sz = 1; 85 86 p = malloc(sz); 87 if (p == NULL) 88 err(EX_TEMPFAIL, "out of memory"); 89 return (p); 90 } 91 92 int 93 main(argc, argv) 94 int argc; 95 char *argv[]; 96 { 97 int ch, debug, i, pdes[2], pid, status; 98 size_t fplen = 0, fptlen = 0, len; 99 off_t offset; 100 SM_FILE_T *fp; 101 char *addrp = NULL, *domain, *p, *t; 102 char *from_path, *from_sys, *from_user; 103 char **args, buf[2048], lbuf[2048]; 104 struct stat sb; 105 extern char *optarg; 106 extern int optind; 107 108 debug = 0; 109 domain = "UUCP"; /* Default "domain". */ 110 while ((ch = getopt(argc, argv, "D:T")) != -1) 111 { 112 switch (ch) 113 { 114 case 'T': 115 debug = 1; 116 break; 117 118 case 'D': 119 domain = optarg; 120 break; 121 122 case '?': 123 default: 124 usage(); 125 } 126 } 127 128 argc -= optind; 129 argv += optind; 130 131 if (argc < 1) 132 usage(); 133 134 from_path = from_sys = from_user = NULL; 135 for (offset = 0; ; ) 136 { 137 /* Get and nul-terminate the line. */ 138 if (sm_io_fgets(smioin, SM_TIME_DEFAULT, lbuf, 139 sizeof(lbuf)) == NULL) 140 err(EX_DATAERR, "no data"); 141 if ((p = strchr(lbuf, '\n')) == NULL) 142 err(EX_DATAERR, "line too long"); 143 *p = '\0'; 144 145 /* Parse lines until reach a non-"From" line. */ 146 if (!strncmp(lbuf, "From ", 5)) 147 addrp = lbuf + 5; 148 else if (!strncmp(lbuf, ">From ", 6)) 149 addrp = lbuf + 6; 150 else if (offset == 0) 151 err(EX_DATAERR, 152 "missing or empty From line: %s", lbuf); 153 else 154 { 155 *p = '\n'; 156 break; 157 } 158 159 if (addrp == NULL || *addrp == '\0') 160 err(EX_DATAERR, "corrupted From line: %s", lbuf); 161 162 /* Use the "remote from" if it exists. */ 163 for (p = addrp; (p = strchr(p + 1, 'r')) != NULL; ) 164 { 165 if (!strncmp(p, "remote from ", 12)) 166 { 167 for (t = p += 12; *t != '\0'; ++t) 168 { 169 if (isascii(*t) && isspace(*t)) 170 break; 171 } 172 *t = '\0'; 173 if (debug) 174 (void) sm_io_fprintf(smioerr, 175 SM_TIME_DEFAULT, 176 "remote from: %s\n", 177 p); 178 break; 179 } 180 } 181 182 /* Else use the string up to the last bang. */ 183 if (p == NULL) 184 { 185 if (*addrp == '!') 186 err(EX_DATAERR, "bang starts address: %s", 187 addrp); 188 else if ((t = strrchr(addrp, '!')) != NULL) 189 { 190 *t = '\0'; 191 p = addrp; 192 addrp = t + 1; 193 if (*addrp == '\0') 194 err(EX_DATAERR, 195 "corrupted From line: %s", lbuf); 196 if (debug) 197 (void) sm_io_fprintf(smioerr, 198 SM_TIME_DEFAULT, 199 "bang: %s\n", p); 200 } 201 } 202 203 /* 'p' now points to any system string from this line. */ 204 if (p != NULL) 205 { 206 /* Nul terminate it as necessary. */ 207 for (t = p; *t != '\0'; ++t) 208 { 209 if (isascii(*t) && isspace(*t)) 210 break; 211 } 212 *t = '\0'; 213 214 /* If the first system, copy to the from_sys string. */ 215 if (from_sys == NULL) 216 { 217 from_sys = newstr(p); 218 if (debug) 219 (void) sm_io_fprintf(smioerr, 220 SM_TIME_DEFAULT, 221 "from_sys: %s\n", 222 from_sys); 223 } 224 225 /* Concatenate to the path string. */ 226 len = t - p; 227 if (from_path == NULL) 228 { 229 fplen = 0; 230 if ((from_path = malloc(fptlen = 256)) == NULL) 231 err(EX_TEMPFAIL, "out of memory"); 232 } 233 if (fplen + len + 2 > fptlen) 234 { 235 fptlen += SM_MAX(fplen + len + 2, 256); 236 if ((from_path = realloc(from_path, 237 fptlen)) == NULL) 238 err(EX_TEMPFAIL, "out of memory"); 239 } 240 memmove(from_path + fplen, p, len); 241 fplen += len; 242 from_path[fplen++] = '!'; 243 from_path[fplen] = '\0'; 244 } 245 246 /* Save off from user's address; the last one wins. */ 247 for (p = addrp; *p != '\0'; ++p) 248 { 249 if (isascii(*p) && isspace(*p)) 250 break; 251 } 252 *p = '\0'; 253 if (*addrp == '\0') 254 addrp = "<>"; 255 if (from_user != NULL) 256 free(from_user); 257 from_user = newstr(addrp); 258 259 if (debug) 260 { 261 if (from_path != NULL) 262 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 263 "from_path: %s\n", 264 from_path); 265 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 266 "from_user: %s\n", from_user); 267 } 268 269 if (offset != -1) 270 offset = (off_t)sm_io_tell(smioin, SM_TIME_DEFAULT); 271 } 272 273 274 /* Allocate args (with room for sendmail args as well as recipients */ 275 args = (char **)xalloc(sizeof(*args) * (10 + argc)); 276 277 i = 0; 278 args[i++] = _PATH_SENDMAIL; /* Build sendmail's argument list. */ 279 args[i++] = "-G"; /* relay submission */ 280 args[i++] = "-oee"; /* No errors, just status. */ 281 #ifdef QUEUE_ONLY 282 args[i++] = "-odq"; /* Queue it, don't try to deliver. */ 283 #else 284 args[i++] = "-odi"; /* Deliver in foreground. */ 285 #endif 286 args[i++] = "-oi"; /* Ignore '.' on a line by itself. */ 287 288 /* set from system and protocol used */ 289 if (from_sys == NULL) 290 sm_snprintf(buf, sizeof(buf), "-p%s", domain); 291 else if (strchr(from_sys, '.') == NULL) 292 sm_snprintf(buf, sizeof(buf), "-p%s:%s.%s", 293 domain, from_sys, domain); 294 else 295 sm_snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys); 296 args[i++] = newstr(buf); 297 298 /* Set name of ``from'' person. */ 299 sm_snprintf(buf, sizeof(buf), "-f%s%s", 300 from_path ? from_path : "", from_user); 301 args[i++] = newstr(buf); 302 303 /* 304 ** Don't copy arguments beginning with - as they will be 305 ** passed to sendmail and could be interpreted as flags. 306 ** To prevent confusion of sendmail wrap < and > around 307 ** the address (helps to pass addrs like @gw1,@gw2:aa@bb) 308 */ 309 310 while (*argv != NULL) 311 { 312 if (**argv == '-') 313 err(EX_USAGE, "dash precedes argument: %s", *argv); 314 315 if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL) 316 args[i++] = *argv; 317 else 318 { 319 len = strlen(*argv) + 3; 320 if ((args[i] = malloc(len)) == NULL) 321 err(EX_TEMPFAIL, "Cannot malloc"); 322 sm_snprintf(args[i++], len, "<%s>", *argv); 323 } 324 argv++; 325 argc--; 326 327 /* Paranoia check, argc used for args[] bound */ 328 if (argc < 0) 329 err(EX_SOFTWARE, "Argument count mismatch"); 330 } 331 args[i] = NULL; 332 333 if (debug) 334 { 335 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 336 "Sendmail arguments:\n"); 337 for (i = 0; args[i] != NULL; i++) 338 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 339 "\t%s\n", args[i]); 340 } 341 342 /* 343 ** If called with a regular file as standard input, seek to the right 344 ** position in the file and just exec sendmail. Could probably skip 345 ** skip the stat, but it's not unreasonable to believe that a failed 346 ** seek will cause future reads to fail. 347 */ 348 349 if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode)) 350 { 351 if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset) 352 err(EX_TEMPFAIL, "stdin seek"); 353 (void) execv(_PATH_SENDMAIL, args); 354 err(EX_OSERR, "%s", _PATH_SENDMAIL); 355 } 356 357 if (pipe(pdes) < 0) 358 err(EX_OSERR, "pipe failed"); 359 360 switch (pid = fork()) 361 { 362 case -1: /* Err. */ 363 err(EX_OSERR, "fork failed"); 364 /* NOTREACHED */ 365 366 case 0: /* Child. */ 367 if (pdes[0] != STDIN_FILENO) 368 { 369 (void) dup2(pdes[0], STDIN_FILENO); 370 (void) close(pdes[0]); 371 } 372 (void) close(pdes[1]); 373 (void) execv(_PATH_SENDMAIL, args); 374 err(EX_UNAVAILABLE, "%s", _PATH_SENDMAIL); 375 /* NOTREACHED */ 376 } 377 378 if ((fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &(pdes[1]), 379 SM_IO_WRONLY, NULL)) == NULL) 380 err(EX_OSERR, "sm_io_open failed"); 381 (void) close(pdes[0]); 382 383 /* Copy the file down the pipe. */ 384 do 385 { 386 (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s", lbuf); 387 } while (sm_io_fgets(smioin, SM_TIME_DEFAULT, lbuf, 388 sizeof(lbuf)) != NULL); 389 390 if (sm_io_error(smioin)) 391 err(EX_TEMPFAIL, "stdin: %s", sm_errstring(errno)); 392 393 if (sm_io_close(fp, SM_TIME_DEFAULT)) 394 err(EX_OSERR, "sm_io_close failed"); 395 396 if ((waitpid(pid, &status, 0)) == -1) 397 err(EX_OSERR, "%s", _PATH_SENDMAIL); 398 399 if (!WIFEXITED(status)) 400 err(EX_OSERR, "%s: did not terminate normally", _PATH_SENDMAIL); 401 402 if (WEXITSTATUS(status)) 403 err(status, "%s: terminated with %d (non-zero) status", 404 _PATH_SENDMAIL, WEXITSTATUS(status)); 405 exit(EX_OK); 406 /* NOTREACHED */ 407 return EX_OK; 408 } 409 410 static void 411 usage() 412 { 413 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, 414 "usage: rmail [-T] [-D domain] user ...\n"); 415 exit(EX_USAGE); 416 } 417 418 static void 419 #ifdef __STDC__ 420 err(int eval, const char *fmt, ...) 421 #else /* __STDC__ */ 422 err(eval, fmt, va_alist) 423 int eval; 424 const char *fmt; 425 va_dcl 426 #endif /* __STDC__ */ 427 { 428 SM_VA_LOCAL_DECL 429 430 if (fmt != NULL) 431 { 432 SM_VA_START(ap, fmt); 433 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "rmail: "); 434 (void) sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap); 435 SM_VA_END(ap); 436 (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "\n"); 437 } 438 exit(eval); 439 } 440