xref: /plan9/sys/src/cmd/ip/imap4d/folder.c (revision 43aadf5e5598b9159a98f64e309c3ae860328a56)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 static	int	copyData(int ffd, int tfd, MbLock *ml);
8 static	MbLock	mLock =
9 {
10 	.fd = -1
11 };
12 
13 static char curDir[MboxNameLen];
14 
15 void
resetCurDir(void)16 resetCurDir(void)
17 {
18 	curDir[0] = '\0';
19 }
20 
21 int
myChdir(char * dir)22 myChdir(char *dir)
23 {
24 	if(strcmp(dir, curDir) == 0)
25 		return 0;
26 	if(dir[0] != '/' || strlen(dir) > MboxNameLen)
27 		return -1;
28 	strcpy(curDir, dir);
29 	if(chdir(dir) < 0){
30 		werrstr("mychdir failed: %r");
31 		return -1;
32 	}
33 	return 0;
34 }
35 
36 int
cdCreate(char * dir,char * file,int mode,ulong perm)37 cdCreate(char *dir, char *file, int mode, ulong perm)
38 {
39 	if(myChdir(dir) < 0)
40 		return -1;
41 	return create(file, mode, perm);
42 }
43 
44 Dir*
cdDirstat(char * dir,char * file)45 cdDirstat(char *dir, char *file)
46 {
47 	if(myChdir(dir) < 0)
48 		return nil;
49 	return dirstat(file);
50 }
51 
52 int
cdExists(char * dir,char * file)53 cdExists(char *dir, char *file)
54 {
55 	Dir *d;
56 
57 	d = cdDirstat(dir, file);
58 	if(d == nil)
59 		return 0;
60 	free(d);
61 	return 1;
62 }
63 
64 int
cdDirwstat(char * dir,char * file,Dir * d)65 cdDirwstat(char *dir, char *file, Dir *d)
66 {
67 	if(myChdir(dir) < 0)
68 		return -1;
69 	return dirwstat(file, d);
70 }
71 
72 int
cdOpen(char * dir,char * file,int mode)73 cdOpen(char *dir, char *file, int mode)
74 {
75 	if(myChdir(dir) < 0)
76 		return -1;
77 	return open(file, mode);
78 }
79 
80 int
cdRemove(char * dir,char * file)81 cdRemove(char *dir, char *file)
82 {
83 	if(myChdir(dir) < 0)
84 		return -1;
85 	return remove(file);
86 }
87 
88 /*
89  * open the one true mail lock file
90  */
91 MbLock*
mbLock(void)92 mbLock(void)
93 {
94 	int i;
95 
96 	if(mLock.fd >= 0)
97 		bye("mail lock deadlock");
98 	for(i = 0; i < 5; i++){
99 		mLock.fd = openLocked(mboxDir, "L.mbox", OREAD);
100 		if(mLock.fd >= 0)
101 			return &mLock;
102 		sleep(1000);
103 	}
104 	return nil;
105 }
106 
107 void
mbUnlock(MbLock * ml)108 mbUnlock(MbLock *ml)
109 {
110 	if(ml != &mLock)
111 		bye("bad mail unlock");
112 	if(ml->fd < 0)
113 		bye("mail unlock when not locked");
114 	close(ml->fd);
115 	ml->fd = -1;
116 }
117 
118 void
mbLockRefresh(MbLock * ml)119 mbLockRefresh(MbLock *ml)
120 {
121 	char buf[1];
122 
123 	seek(ml->fd, 0, 0);
124 	read(ml->fd, buf, 1);
125 }
126 
127 int
mbLocked(void)128 mbLocked(void)
129 {
130 	return mLock.fd >= 0;
131 }
132 
133 char*
impName(char * name)134 impName(char *name)
135 {
136 	char *s;
137 	int n;
138 
139 	if(cistrcmp(name, "inbox") == 0)
140 		if(access("msgs", AEXIST) == 0)
141 			name = "msgs";
142 		else
143 			name = "mbox";
144 	n = strlen(name) + STRLEN(".imp") + 1;
145 	s = binalloc(&parseBin, n, 0);
146 	if(s == nil)
147 		return nil;
148 	snprint(s, n, "%s.imp", name);
149 	return s;
150 }
151 
152 /*
153  * massage the mailbox name into something valid
154  * eliminates all .', and ..',s, redundatant and trailing /'s.
155  */
156 char *
mboxName(char * s)157 mboxName(char *s)
158 {
159 	char *ss;
160 
161 	ss = mutf7str(s);
162 	if(ss == nil)
163 		return nil;
164 	cleanname(ss);
165 	return ss;
166 }
167 
168 char *
strmutf7(char * s)169 strmutf7(char *s)
170 {
171 	char *m;
172 	int n;
173 
174 	n = strlen(s) * MUtf7Max + 1;
175 	m = binalloc(&parseBin, n, 0);
176 	if(m == nil)
177 		return nil;
178 	if(encmutf7(m, n, s) < 0)
179 		return nil;
180 	return m;
181 }
182 
183 char *
mutf7str(char * s)184 mutf7str(char *s)
185 {
186 	char *m;
187 	int n;
188 
189 	/*
190 	 * n = strlen(s) * UTFmax / (2.67) + 1
191 	 * UTFMax / 2.67 == 3 / (8/3) == 9 / 8
192 	 */
193 	n = strlen(s);
194 	n = (n * 9 + 7) / 8 + 1;
195 	m = binalloc(&parseBin, n, 0);
196 	if(m == nil)
197 		return nil;
198 	if(decmutf7(m, n, s) < 0)
199 		return nil;
200 	return m;
201 }
202 
203 void
splitr(char * s,int c,char ** left,char ** right)204 splitr(char *s, int c, char **left, char **right)
205 {
206 	char *d;
207 	int n;
208 
209 	n = strlen(s);
210 	d = binalloc(&parseBin, n + 1, 0);
211 	if(d == nil)
212 		parseErr("out of memory");
213 	strcpy(d, s);
214 	s = strrchr(d, c);
215 	if(s != nil){
216 		*left = d;
217 		*s++ = '\0';
218 		*right = s;
219 	}else{
220 		*right = d;
221 		*left = d + n;
222 	}
223 }
224 
225 /*
226  * create the mailbox and all intermediate components
227  * a trailing / implies the new mailbox is a directory;
228  * otherwise, it's a file.
229  *
230  * return with the file open for write, or directory open for read.
231  */
232 int
createBox(char * mbox,int dir)233 createBox(char *mbox, int dir)
234 {
235 	char *m;
236 	int fd;
237 
238 	fd = -1;
239 	for(m = mbox; *m; m++){
240 		if(*m == '/'){
241 			*m = '\0';
242 			if(access(mbox, AEXIST) < 0){
243 				if(fd >= 0)
244 					close(fd);
245 				fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
246 				if(fd < 0)
247 					return -1;
248 			}
249 			*m = '/';
250 		}
251 	}
252 	if(dir)
253 		fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
254 	else
255 		fd = cdCreate(mboxDir, mbox, OWRITE, 0664);
256 	return fd;
257 }
258 
259 /*
260  * move one mail folder to another
261  * destination mailbox doesn't exist.
262  * the source folder may be a directory or a mailbox,
263  * and may be in the same directory as the destination,
264  * or a completely different directory.
265  */
266 int
moveBox(char * from,char * to)267 moveBox(char *from, char *to)
268 {
269 	Dir *d;
270 	char *fd, *fe, *td, *te, *fimp;
271 
272 	splitr(from, '/', &fd, &fe);
273 	splitr(to, '/', &td, &te);
274 
275 	/*
276 	 * in the same directory: try rename
277 	 */
278 	d = cdDirstat(mboxDir, from);
279 	if(d == nil)
280 		return 0;
281 	if(strcmp(fd, td) == 0){
282 		nulldir(d);
283 		d->name = te;
284 		if(cdDirwstat(mboxDir, from, d) >= 0){
285 			fimp = impName(from);
286 			d->name = impName(te);
287 			cdDirwstat(mboxDir, fimp, d);
288 			free(d);
289 			return 1;
290 		}
291 	}
292 
293 	/*
294 	 * directory copy is too hard for now
295 	 */
296 	if(d->mode & DMDIR)
297 		return 0;
298 	free(d);
299 
300 	return copyBox(from, to, 1);
301 }
302 
303 /*
304  * copy the contents of one mailbox to another
305  * either truncates or removes the source box if it succeeds.
306  */
307 int
copyBox(char * from,char * to,int doremove)308 copyBox(char *from, char *to, int doremove)
309 {
310 	MbLock *ml;
311 	char *fimp, *timp;
312 	int ffd, tfd, ok;
313 
314 	if(cistrcmp(from, "inbox") == 0)
315 		if(access("msgs", AEXIST) == 0)
316 			from = "msgs";
317 		else
318 			from = "mbox";
319 
320 	ml = mbLock();
321 	if(ml == nil)
322 		return 0;
323 	ffd = openLocked(mboxDir, from, OREAD);
324 	if(ffd < 0){
325 		mbUnlock(ml);
326 		return 0;
327 	}
328 	tfd = createBox(to, 0);
329 	if(tfd < 0){
330 		mbUnlock(ml);
331 		close(ffd);
332 		return 0;
333 	}
334 
335 	ok = copyData(ffd, tfd, ml);
336 	close(ffd);
337 	close(tfd);
338 	if(!ok){
339 		mbUnlock(ml);
340 		return 0;
341 	}
342 
343 	fimp = impName(from);
344 	timp = impName(to);
345 	if(fimp != nil && timp != nil){
346 		ffd = cdOpen(mboxDir, fimp, OREAD);
347 		if(ffd >= 0){
348 			tfd = cdCreate(mboxDir, timp, OWRITE, 0664);
349 			if(tfd >= 0){
350 				copyData(ffd, tfd, ml);
351 				close(tfd);
352 			}
353 			close(ffd);
354 		}
355 	}
356 	cdRemove(mboxDir, fimp);
357 	if(doremove)
358 		cdRemove(mboxDir, from);
359 	else
360 		close(cdOpen(mboxDir, from, OWRITE|OTRUNC));
361 	mbUnlock(ml);
362 	return 1;
363 }
364 
365 /*
366  * copies while holding the mail lock,
367  * then tries to copy permissions and group ownership
368  */
369 static int
copyData(int ffd,int tfd,MbLock * ml)370 copyData(int ffd, int tfd, MbLock *ml)
371 {
372 	Dir *fd, td;
373 	char buf[BufSize];
374 	int n;
375 
376 	for(;;){
377 		n = read(ffd, buf, BufSize);
378 		if(n <= 0){
379 			if(n < 0)
380 				return 0;
381 			break;
382 		}
383 		if(write(tfd, buf, n) != n)
384 			return 0;
385 		mbLockRefresh(ml);
386 	}
387 	fd = dirfstat(ffd);
388 	if(fd != nil){
389 		nulldir(&td);
390 		td.mode = fd->mode;
391 		if(dirfwstat(tfd, &td) >= 0){
392 			nulldir(&td);
393 			td.gid = fd->gid;
394 			dirfwstat(tfd, &td);
395 		}
396 	}
397 	return 1;
398 }
399