1 /*
2  * mdconvert - Maildir UID scheme converter
3  * Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <autodefs.h>
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #include <stdarg.h>
27 #include <dirent.h>
28 #include <limits.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <ctype.h>
32 
33 #include <db.h>
34 
35 #define EXE "mdconvert"
36 
37 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
38 # define ATTR_NORETURN __attribute__((noreturn))
39 # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
40 #else
41 # define ATTR_NORETURN
42 # define ATTR_PRINTFLIKE(fmt,var)
43 #endif
44 
45 static void ATTR_NORETURN
oob(void)46 oob( void )
47 {
48 	fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
49 	abort();
50 }
51 
52 static void ATTR_PRINTFLIKE(1, 2)
sys_error(const char * msg,...)53 sys_error( const char *msg, ... )
54 {
55 	va_list va;
56 	char buf[1024];
57 
58 	va_start( va, msg );
59 	if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf))
60 		oob();
61 	va_end( va );
62 	perror( buf );
63 }
64 
65 static int ATTR_PRINTFLIKE(3, 4)
nfsnprintf(char * buf,int blen,const char * fmt,...)66 nfsnprintf( char *buf, int blen, const char *fmt, ... )
67 {
68 	int ret;
69 	va_list va;
70 
71 	va_start( va, fmt );
72 	if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
73 		oob();
74 	va_end( va );
75 	return ret;
76 }
77 
78 static const char *subdirs[] = { "cur", "new" };
79 static struct flock lck;
80 static DBT key, value;
81 
82 static int
convert(const char * box,int altmap)83 convert( const char *box, int altmap )
84 {
85 	DB *db;
86 	DIR *d;
87 	struct dirent *e;
88 	const char *u, *ru;
89 	char *p, *s, *dpath, *spath, *dbpath;
90 	int i, n, ret, sfd, dfd, bl, ml, uv[2], uid;
91 	struct stat st;
92 	char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX];
93 	char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX];
94 
95 	if (stat( box, &st ) || !S_ISDIR(st.st_mode)) {
96 		fprintf( stderr, "'%s' is no Maildir mailbox.\n", box );
97 		return 1;
98 	}
99 
100 	nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box );
101 	nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box );
102 	if (altmap)
103 		dpath = umpath, spath = uvpath, dbpath = tdpath;
104 	else
105 		spath = umpath, dpath = uvpath, dbpath = umpath;
106 	nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath );
107 	if ((sfd = open( spath, O_RDWR )) < 0) {
108 		if (errno != ENOENT)
109 			sys_error( "Cannot open %s", spath );
110 		return 1;
111 	}
112 	if (fcntl( sfd, F_SETLKW, &lck )) {
113 		sys_error( "Cannot lock %s", spath );
114 		goto sbork;
115 	}
116 	if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) {
117 		sys_error( "Cannot create %s", tdpath );
118 		goto sbork;
119 	}
120 	if (db_create( &db, NULL, 0 )) {
121 		fputs( "Error: db_create() failed\n", stderr );
122 		goto tbork;
123 	}
124 	if ((ret = (db->open)( db, NULL, dbpath, NULL, DB_HASH, altmap ? DB_CREATE|DB_TRUNCATE : 0, 0 ))) {
125 		db->err( db, ret, "Error: db->open(%s)", dbpath );
126 	  dbork:
127 		db->close( db, 0 );
128 	  tbork:
129 		unlink( tdpath );
130 		close( dfd );
131 	  sbork:
132 		close( sfd );
133 		return 1;
134 	}
135 	key.data = (void *)"UIDVALIDITY";
136 	key.size = 11;
137 	if (altmap) {
138 		if ((n = read( sfd, buf, sizeof(buf) - 1 )) <= 0 ||
139 		    (buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2))
140 		{
141 			fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box );
142 			goto dbork;
143 		}
144 		value.data = uv;
145 		value.size = sizeof(uv);
146 		if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
147 			db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box );
148 			goto dbork;
149 		}
150 	} else {
151 		if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
152 			db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box );
153 			goto dbork;
154 		}
155 		n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] );
156 		if (write( dfd, buf, n ) != n) {
157 			fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box );
158 			goto dbork;
159 		}
160 	}
161 
162   again:
163 	for (i = 0; i < 2; i++) {
164 		bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] );
165 		if (!(d = opendir( buf ))) {
166 			sys_error( "Cannot list %s", buf );
167 			goto dbork;
168 		}
169 		while ((e = readdir( d ))) {
170 			if (*e->d_name == '.')
171 				continue;
172 			nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name );
173 			memcpy( buf2, buf, bl );
174 			p = strstr( e->d_name, ",U=" );
175 			if (p)
176 				for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++);
177 			else
178 				u = ru = strchr( e->d_name, ':' );
179 			if (u)
180 				ml = u - e->d_name;
181 			else
182 				ru = "", ml = sizeof(buf);
183 			if (altmap) {
184 				if (!p)
185 					continue;
186 				key.data = e->d_name;
187 				key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name);
188 				uid = atoi( p + 3 );
189 				value.data = &uid;
190 				value.size = sizeof(uid);
191 				if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
192 					db->err( db, ret, "Error: cannot write UID for '%s'", box );
193 					goto ebork;
194 				}
195 				nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru );
196 			} else {
197 				s = strpbrk( e->d_name, ",:" );
198 				key.data = e->d_name;
199 				key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name );
200 				if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
201 					if (ret != DB_NOTFOUND) {
202 						db->err( db, ret, "Error: cannot read UID for '%s'", box );
203 						goto ebork;
204 					}
205 					continue;
206 				}
207 				uid = *(int *)value.data;
208 				nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru );
209 			}
210 			if (rename( buf, buf2 )) {
211 				if (errno == ENOENT) {
212 					closedir( d );
213 					goto again;
214 				}
215 				sys_error( "Cannot rename %s to %s", buf, buf2 );
216 			  ebork:
217 				closedir( d );
218 				goto dbork;
219 			}
220 
221 		}
222 		closedir( d );
223 	}
224 
225 	db->close( db, 0 );
226 	close( dfd );
227 	if (rename( tdpath, dpath )) {
228 		sys_error( "Cannot rename %s to %s", tdpath, dpath );
229 		close( sfd );
230 		return 1;
231 	}
232 	if (unlink( spath ))
233 		sys_error( "Cannot remove %s", spath );
234 	close( sfd );
235 	return 0;
236 }
237 
238 int
main(int argc,char ** argv)239 main( int argc, char **argv )
240 {
241 	int oint, ret, altmap = 0;
242 
243 	for (oint = 1; oint < argc; oint++) {
244 		if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) {
245 			puts(
246 "Usage: " EXE " [-a] mailbox...\n"
247 "  -a, --alt      convert to alternative (DB based) UID scheme\n"
248 "  -n, --native   convert to native (file name based) UID scheme (default)\n"
249 "  -h, --help     show this help message\n"
250 "  -v, --version  display version"
251 			);
252 			return 0;
253 		} else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) {
254 			puts( EXE " " VERSION " - Maildir UID scheme converter" );
255 			return 0;
256 		} else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) {
257 			altmap = 1;
258 		} else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) {
259 			altmap = 0;
260 		} else if (!strcmp( argv[oint], "--" )) {
261 			oint++;
262 			break;
263 		} else if (argv[oint][0] == '-') {
264 			fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] );
265 			return 1;
266 		} else
267 			break;
268 	}
269 	if (oint == argc) {
270 		fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" );
271 		return 1;
272 	}
273 #if SEEK_SET != 0
274 	lck.l_whence = SEEK_SET;
275 #endif
276 #if F_WRLCK != 0
277 	lck.l_type = F_WRLCK;
278 #endif
279 	ret = 0;
280 	for (; oint < argc; oint++)
281 		ret |= convert( argv[oint], altmap );
282 	return ret;
283 }
284 
285