1/*
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 * $FreeBSD$
10 *
11 */
12
13#include "ctm.h"
14#define BADREAD 32
15
16/*---------------------------------------------------------------------------*/
17/* Pass3 -- Validate the incoming CTM-file.
18 */
19
20int
21settime(const char *name, const struct timeval *times)
22{
23	if (SetTime)
24	    if (utimes(name,times)) {
25		warn("utimes(): %s", name);
26		return -1;
27	    }
28	return 0;
29}
30
31int
32Pass3(FILE *fd)
33{
34    u_char *p,*q,buf[BUFSIZ];
35    MD5_CTX ctx;
36    int i,j,sep,cnt;
37    u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0;
38    struct CTM_Syntax *sp;
39    FILE *ed=0;
40    struct stat st;
41    char md5_1[33];
42    int match=0;
43    struct timeval times[2];
44    struct CTM_Filter *filter = NULL;
45    if(Verbose>3)
46	printf("Pass3 -- Applying the CTM-patch\n");
47    MD5Init (&ctx);
48
49    GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
50    GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
51    GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
52    GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
53    GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
54    GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
55
56    /*
57     * This would be cleaner if mktime() worked in UTC rather than
58     * local time.
59     */
60    if (SetTime) {
61        struct tm tm;
62        char *tz;
63        char buf[5];
64        int i;
65
66#define SUBSTR(off,len)	strncpy(buf, &TimeStamp[off], len), buf[len] = '\0'
67#define WRONGDATE { fprintf(stderr, " %s failed date validation\n",\
68	TimeStamp); WRONG}
69
70        if (strlen(TimeStamp) != 15 || TimeStamp[14] != 'Z') WRONGDATE
71	for (i = 0; i < 14; i++)
72	    if (!isdigit(TimeStamp[i])) WRONGDATE
73
74        tz = getenv("TZ");
75	if (setenv("TZ", "UTC", 1) < 0) WRONG
76	tzset();
77
78	tm.tm_isdst = tm.tm_gmtoff = 0;
79
80        SUBSTR(0, 4);
81        tm.tm_year = atoi(buf) - 1900;
82        SUBSTR(4, 2);
83        tm.tm_mon = atoi(buf) - 1;
84        if (tm.tm_mon < 0 || tm.tm_mon > 11) WRONGDATE
85        SUBSTR(6, 2);
86        tm.tm_mday = atoi(buf);
87        if (tm.tm_mday < 1 || tm.tm_mday > 31) WRONG;
88        SUBSTR(8, 2);
89        tm.tm_hour = atoi(buf);
90        if (tm.tm_hour > 24) WRONGDATE
91        SUBSTR(10, 2);
92        tm.tm_min = atoi(buf);
93        if (tm.tm_min > 59) WRONGDATE
94        SUBSTR(12, 2);
95        tm.tm_sec = atoi(buf);
96        if (tm.tm_min > 62) WRONGDATE	/* allow leap seconds */
97
98        times[0].tv_sec = times[1].tv_sec = mktime(&tm);
99        if (times[0].tv_sec == -1) WRONGDATE
100	times[0].tv_usec = times[1].tv_usec = 0;
101
102        if (tz) {
103            if (setenv("TZ", tz, 1) < 0) WRONGDATE
104         } else {
105            unsetenv("TZ");
106        }
107    }
108
109    for(;;) {
110	Delete(md5);
111	Delete(uid);
112	Delete(gid);
113	Delete(mode);
114	Delete(md5before);
115	Delete(trash);
116	Delete(name);
117	cnt = -1;
118
119	GETFIELD(p,' ');
120
121	if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
122
123	if(!strcmp(p+3,"_END"))
124	    break;
125
126	for(sp=Syntax;sp->Key;sp++)
127	    if(!strcmp(p+3,sp->Key))
128		goto found;
129	WRONG
130    found:
131	for(i=0;(j = sp->List[i]);i++) {
132	    if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
133		sep = ' ';
134	    else
135		sep = '\n';
136
137	    switch (j & CTM_F_MASK) {
138		case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break;
139		case CTM_F_Uid:  GETFIELDCOPY(uid,sep); break;
140		case CTM_F_Gid:  GETFIELDCOPY(gid,sep); break;
141		case CTM_F_Mode: GETFIELDCOPY(mode,sep); break;
142		case CTM_F_MD5:
143		    if(j & CTM_Q_MD5_Before)
144			GETFIELDCOPY(md5before,sep);
145		    else
146			GETFIELDCOPY(md5,sep);
147		    break;
148		case CTM_F_Count: GETBYTECNT(cnt,sep); break;
149		case CTM_F_Bytes: GETDATA(trash,cnt); break;
150		default: WRONG
151		}
152	    }
153	/* XXX This should go away.  Disallow trailing '/' */
154	j = strlen(name)-1;
155	if(name[j] == '/') name[j] = '\0';
156
157	/*
158	 * If a filter list is specified, run thru the filter list and
159	 * match `name' against filters.  If the name matches, set the
160	 * required action to that specified in the filter.
161	 * The default action if no filterlist is given is to match
162	 * everything.
163	 */
164
165	match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
166	for (filter = FilterList; filter; filter = filter->Next) {
167	    if (0 == regexec(&filter->CompiledRegex, name,
168		0, 0, 0)) {
169		match = filter->Action;
170	    }
171	}
172
173	if (CTM_FILTER_DISABLE == match) /* skip file if disabled */
174		continue;
175
176	if (Verbose > 0)
177		fprintf(stderr,"> %s %s\n",sp->Key,name);
178	if(!strcmp(sp->Key,"FM") || !strcmp(sp->Key, "FS")) {
179	    i = open(name,O_WRONLY|O_CREAT|O_TRUNC,0666);
180	    if(i < 0) {
181		warn("%s", name);
182		WRONG
183	    }
184	    if(cnt != write(i,trash,cnt)) {
185		warn("%s", name);
186		WRONG
187	    }
188	    close(i);
189	    if(strcmp(md5,MD5File(name,md5_1))) {
190		fprintf(stderr,"  %s %s MD5 didn't come out right\n",
191		   sp->Key,name);
192		WRONG
193	    }
194	    if (settime(name,times)) WRONG
195	    continue;
196	}
197	if(!strcmp(sp->Key,"FE")) {
198	    ed = popen("ed","w");
199	    if(!ed) {
200		WRONG
201	    }
202	    fprintf(ed,"e %s\n",name);
203	    if(cnt != fwrite(trash,1,cnt,ed)) {
204		warn("%s", name);
205		pclose(ed);
206		WRONG
207	    }
208	    fprintf(ed,"w %s\n",name);
209	    if(pclose(ed)) {
210		warn("ed");
211		WRONG
212	    }
213	    if(strcmp(md5,MD5File(name,md5_1))) {
214		fprintf(stderr,"  %s %s MD5 didn't come out right\n",
215		   sp->Key,name);
216		WRONG
217	    }
218	    if (settime(name,times)) WRONG
219	    continue;
220	}
221	if(!strcmp(sp->Key,"FN")) {
222	    strcpy(buf,name);
223	    strcat(buf,TMPSUFF);
224	    i = ctm_edit(trash,cnt,name,buf);
225	    if(i) {
226		fprintf(stderr," %s %s Edit failed with code %d.\n",
227		    sp->Key,name,i);
228	        WRONG
229	    }
230	    if(strcmp(md5,MD5File(buf,md5_1))) {
231		fprintf(stderr," %s %s Edit failed MD5 check.\n",
232		    sp->Key,name);
233	        WRONG
234	    }
235	    if (rename(buf,name) == -1)
236		WRONG
237	    if (settime(name,times)) WRONG
238	    continue;
239	}
240	if(!strcmp(sp->Key,"DM")) {
241	    if(0 > mkdir(name,0777)) {
242		sprintf(buf,"mkdir -p %s",name);
243		system(buf);
244	    }
245	    if(0 > stat(name,&st) || ((st.st_mode & S_IFMT) != S_IFDIR)) {
246		fprintf(stderr,"<%s> mkdir failed\n",name);
247		WRONG
248	    }
249	    if (settime(name,times)) WRONG
250	    continue;
251	}
252	if(!strcmp(sp->Key,"FR")) {
253	    if (KeepIt) {
254		if (Verbose > 1)
255			printf("<%s> not removed\n", name);
256	    }
257	    else if (0 != unlink(name)) {
258		fprintf(stderr,"<%s> unlink failed\n",name);
259		if (!Force)
260		    WRONG
261	    }
262	    continue;
263	}
264	if(!strcmp(sp->Key,"DR")) {
265	    /*
266	     * We cannot use rmdir() because we do not get the directories
267	     * in '-depth' order (cvs-cur.0018.gz for examples)
268	     */
269	    if (KeepIt) {
270		if (Verbose > 1) {
271			printf("<%s> not removed\n", name);
272		}
273	    } else {
274		    sprintf(buf,"rm -rf %s",name);
275		    system(buf);
276	    }
277	    continue;
278	}
279	WRONG
280    }
281
282    Delete(md5);
283    Delete(uid);
284    Delete(gid);
285    Delete(mode);
286    Delete(md5before);
287    Delete(trash);
288    Delete(name);
289
290    q = MD5End (&ctx,md5_1);
291    GETFIELD(p,'\n');
292    if(strcmp(q,p)) WRONG
293    if (-1 != getc(fd)) WRONG
294    return 0;
295}
296