1/************************************************************************
2 *	Collection of NFS resistant exclusive creat routines		*
3 *									*
4 *	Copyright (c) 1990-1997, S.R. van den Berg, The Netherlands	*
5 *	Copyright (c) 1999-2001, Philip Guenther, The United States	*
6 *							of America	*
7 *	#include "../README"						*
8 ************************************************************************/
9#ifdef RCS
10static /*const*/char rcsid[]=
11 "$Id: exopen.c,v 1.44 2001/08/26 21:10:11 guenther Exp $";
12#endif
13#include "procmail.h"
14#include "acommon.h"
15#include "robust.h"
16#include "misc.h"
17#include "exopen.h"
18#include "lastdirsep.h"
19#include "sublib.h"
20
21static const char*safehost P((void)) /* return a hostname safe for filenames */
22{ static const char*sname=0;
23  if(!sname)
24   { char*to;const char*from=hostname();
25     int badchars=0;
26     for(to=(char*)from;*to;to++)     /* check for characters that shouldn't */
27	if(*to=='/'||*to==':'||*to=='\\')		  /* be in hostnames */
28	   badchars++;
29     if(!badchars)			      /* it's clean, pass it through */
30	sname=from;
31     else if(!(sname=to=malloc(3*badchars+strlen(from)+1)))
32	sname="";					       /* no memory! */
33     else
34      { int c;
35	while(badchars)
36	   switch(c=(unsigned char)*from++)
37	    { default:*to++=c;
38		 break;
39	      case '\0':from--;to--;		     /* "this cannot happen" */
40		 break;
41	      case '/':case ':':case '\\':	 /* we'll remap them to \ooo */
42		 *to++='\\';
43		 *to++='0'+(c>>6);
44		 *to++='0'+((c>>3)&7);
45		 *to++='0'+(c&7);
46		 badchars--;
47	    }
48	strcpy(to,from);		    /* copy the remaining characters */
49      }
50   }
51  return sname;
52}
53
54int unique(full,p,len,mode,verbos,flags)char*const full;char*p;
55 const size_t len;const mode_t mode;const int verbos,flags;
56{ static const char s2c[]=".,+%";static int serial=STRLEN(s2c);
57  static time_t t;char*dot,*end,*host;struct stat filebuf;
58  int nicediff,i,didnice,retry=RETRYunique;
59  if(flags&doCHOWN)		  /* semi-critical, try raising the priority */
60   { nicediff=nice(0);SETerrno(0);nicediff-=nice(-NICE_RANGE);
61     didnice=!errno;					  /* did we succeed? */
62   }
63  end=full+len;
64  if(end-p<=UNIQnamelen-1)		      /* were we given enough space? */
65     goto ret0;						    /* nope, give up */
66  if(flags&doMAILDIR)				/* 'official' maildir format */
67     dot=p;
68  else						     /* 'traditional' format */
69     *p=UNIQ_PREFIX,dot=ultoan((unsigned long)thepid,p+1);
70  if(serial<STRLEN(s2c))
71     goto in;
72  do
73   { if(serial>STRLEN(s2c)-1)			     /* roll over the count? */
74      { ;{ time_t t2;
75	   while(t==(t2=time((time_t*)0)))	/* make sure time has passed */
76	      ssleep(1);				   /* tap tap tap... */
77	   serial=0;t=t2;
78	 }
79in:	if(flags&doMAILDIR)
80	 { dot=ultstr(0,(unsigned long)t,p);	      /* time.pid_s.hostname */
81	   *dot='.';
82	   dot=ultstr(0,(unsigned long)thepid,dot+1);
83	   *dot++='_';
84	   host=dot+2;
85	 }
86	else
87	   host=1+ultoan((unsigned long)t,dot+1);	/* _pid%time.hostname */
88	host[-1]='.';				  /* add the ".hostname" part */
89	strlcpy(host,safehost(),end-host);
90      }
91     *dot=(flags&doMAILDIR)?'0'+serial:s2c[serial];
92     serial++;
93     i=lstat(full,&filebuf);
94#ifdef ENAMETOOLONG
95     if(i&&errno==ENAMETOOLONG)
96      { char*op,*ldp;
97	op=lastdirsep(full);
98	ldp=op+1;		   /* keep track to avoid shortening past it */
99	if(end-op>MINnamelen+1)			   /* guess at a safe length */
100	   op+=MINnamelen+1;			  /* start at MINnamelen out */
101	else
102	   op=end-1;			    /* this shouldn't happen, but... */
103	do
104	   *--op='\0';					     /* try chopping */
105	while((i=lstat(full,&filebuf))&&errno==ENAMETOOLONG&&op>ldp);
106      }	      /* either it stopped being a problem or we ran out of filename */
107#endif
108   }
109#ifndef O_CREAT
110#define ropen(path,type,mode)	creat(path,mode)
111#endif
112  while((!i||errno!=ENOENT||	      /* casually check if it already exists */
113	 (0>(i=ropen(full,O_WRONLY|O_CREAT|O_EXCL,mode))&&errno==EEXIST))&&
114	(i= -1,retry--));
115  if(flags&doCHOWN&&didnice)
116     nice(nicediff);		   /* put back the priority to the old level */
117  if(i<0)
118   { if(verbos)			      /* this error message can be confusing */
119	writeerr(full);					 /* for casual users */
120     goto ret0;
121   }
122#ifdef NOfstat
123  if(flags&doCHOWN)
124   { if(
125#else
126  if(flags&doCHECK)
127   { struct stat fdbuf;
128     fstat(i,&fdbuf);			/* match between the file descriptor */
129#define NEQ(what)	(fdbuf.what!=filebuf.what)	    /* and the file? */
130     if(lstat(full,&filebuf)||filebuf.st_nlink!=1||filebuf.st_size||
131	NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)||
132	 flags&doCHOWN&&
133#endif
134	 chown(full,uid,sgid))
135      { rclose(i);unlink(full);			 /* forget it, no permission */
136ret0:	return flags&doFD?-1:0;
137      }
138   }
139  if(flags&doLOCK)
140     rwrite(i,"0",1);			   /* pid 0, `works' across networks */
141  if(flags&doFD)
142     return i;
143  rclose(i);
144  return 1;
145}
146				     /* rename MUST fail if already existent */
147int myrename(old,newn)const char*const old,*const newn;
148{ int fd,serrno;
149  fd=hlink(old,newn);serrno=errno;unlink(old);
150  if(fd>0)rclose(fd-1);
151  SETerrno(serrno);
152  return fd<0?-1:0;
153}
154
155						     /* NFS-resistant link() */
156int rlink(old,newn,st)const char*const old,*const newn;struct stat*st;
157{ if(link(old,newn))
158   { register int serrno,ret;struct stat sto,stn;
159     serrno=errno;ret= -1;
160#undef NEQ			       /* compare files to see if the link() */
161#define NEQ(what)	(sto.what!=stn.what)	       /* actually succeeded */
162     if(lstat(old,&sto)||(ret=1,lstat(newn,&stn)||
163	NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)||
164	S_ISLNK(sto.st_mode)))			    /* symlinks are also bad */
165      { SETerrno(serrno);
166	if(st&&ret>0)
167	 { *st=sto;				       /* save the stat data */
168	   return ret;				    /* it was a real failure */
169	 }
170	return -1;
171      }
172     /*SETerrno(serrno);*/   /* we really succeeded, don't bother with errno */
173   }
174  return 0;
175}
176		 /* hardlink with fallback for systems that don't support it */
177int hlink(old,newn)const char*const old,*const newn;
178{ int ret;struct stat stbuf;
179  if(0<(ret=rlink(old,newn,&stbuf)))		      /* try a real hardlink */
180   { int fd;
181#ifdef O_CREAT				       /* failure due to filesystem? */
182     if(stbuf.st_nlink<2&&errno==EXDEV&&     /* try it by conventional means */
183	0<=(fd=ropen(newn,O_WRONLY|O_CREAT|O_EXCL,stbuf.st_mode)))
184	return fd+1;
185#endif
186     return -1;
187   }
188  return ret;				 /* success, or the stat failed also */
189}
190