1 /* $OpenBSD: client.c,v 1.129 2020/10/19 19:51:20 naddy Exp $ */
2 /*
3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/types.h>
19 #include <sys/dirent.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <libgen.h>
26 #include <limits.h>
27 #include <pwd.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include "cvs.h"
34 #include "remote.h"
35
36 struct cvs_req cvs_requests[] = {
37 /* this is what our client will use, the server should support it */
38 { "Root", 1, cvs_server_root, REQ_NEEDED },
39 { "Valid-responses", 1, cvs_server_validresp, REQ_NEEDED },
40 { "valid-requests", 1, cvs_server_validreq, REQ_NEEDED },
41 { "Directory", 0, cvs_server_directory, REQ_NEEDED },
42 { "Static-directory", 0, cvs_server_static_directory,
43 REQ_NEEDED | REQ_NEEDDIR },
44 { "Sticky", 0, cvs_server_sticky,
45 REQ_NEEDED | REQ_NEEDDIR },
46 { "Entry", 0, cvs_server_entry,
47 REQ_NEEDED | REQ_NEEDDIR },
48 { "Modified", 0, cvs_server_modified,
49 REQ_NEEDED | REQ_NEEDDIR },
50 { "UseUnchanged", 0, cvs_server_useunchanged, REQ_NEEDED },
51 { "Unchanged", 0, cvs_server_unchanged,
52 REQ_NEEDED | REQ_NEEDDIR },
53 { "Questionable", 0, cvs_server_questionable, REQ_NEEDED },
54 { "Argument", 0, cvs_server_argument, REQ_NEEDED },
55 { "Argumentx", 0, cvs_server_argumentx, REQ_NEEDED },
56 { "Global_option", 0, cvs_server_globalopt, REQ_NEEDED },
57 { "Set", 0, cvs_server_set, REQ_NEEDED },
58 { "expand-modules", 0, cvs_server_exp_modules, 0 },
59
60 /*
61 * used to tell the server what is going on in our
62 * working copy, unsupported until we are told otherwise
63 */
64 { "Max-dotdot", 0, NULL, 0 },
65 { "Checkin-prog", 0, NULL, 0 },
66 { "Update-prog", 0, NULL, 0 },
67 { "Kopt", 0, NULL, 0 },
68 { "Checkin-time", 0, NULL, 0 },
69 { "Is-modified", 0, NULL, 0 },
70 { "Notify", 0, NULL, 0 },
71 { "Case", 0, NULL, 0 },
72 { "Gzip-stream", 0, NULL, 0 },
73 { "wrapper-sendme-rcsOptions", 0, NULL, 0 },
74 { "Kerberos-encrypt", 0, NULL, 0 },
75 { "Gssapi-encrypt", 0, NULL, 0 },
76 { "Gssapi-authenticate", 0, NULL, 0 },
77
78 /* commands that might be supported */
79 { "ci", 0, cvs_server_commit, REQ_NEEDDIR },
80 { "co", 0, cvs_server_checkout, REQ_NEEDDIR },
81 { "update", 0, cvs_server_update, REQ_NEEDDIR },
82 { "diff", 0, cvs_server_diff, REQ_NEEDDIR },
83 { "log", 0, cvs_server_log, REQ_NEEDDIR },
84 { "rlog", 0, cvs_server_rlog, 0 },
85 { "add", 0, cvs_server_add, REQ_NEEDDIR },
86 { "remove", 0, cvs_server_remove, REQ_NEEDDIR },
87 { "update-patches", 0, cvs_server_update_patches, 0 },
88 { "gzip-file-contents", 0, NULL, 0 },
89 { "status", 0, cvs_server_status, REQ_NEEDDIR },
90 { "rdiff", 0, cvs_server_rdiff, 0 },
91 { "tag", 0, cvs_server_tag, REQ_NEEDDIR },
92 { "rtag", 0, cvs_server_rtag, 0 },
93 { "import", 0, cvs_server_import, REQ_NEEDDIR },
94 { "admin", 0, cvs_server_admin, REQ_NEEDDIR },
95 { "export", 0, cvs_server_export, REQ_NEEDDIR },
96 { "history", 0, NULL, 0 },
97 { "release", 0, cvs_server_release, REQ_NEEDDIR },
98 { "watch-on", 0, NULL, 0 },
99 { "watch-off", 0, NULL, 0 },
100 { "watch-add", 0, NULL, 0 },
101 { "watch-remove", 0, NULL, 0 },
102 { "watchers", 0, NULL, 0 },
103 { "editors", 0, NULL, 0 },
104 { "init", 0, cvs_server_init, 0 },
105 { "annotate", 0, cvs_server_annotate, REQ_NEEDDIR },
106 { "rannotate", 0, cvs_server_rannotate, 0 },
107 { "noop", 0, NULL, 0 },
108 { "version", 0, cvs_server_version, 0 },
109 { "", -1, NULL, 0 }
110 };
111
112 static void client_check_directory(char *, char *);
113 static char *client_get_supported_responses(void);
114 static char *lastdir = NULL;
115 static int end_of_response = 0;
116
117 static void cvs_client_initlog(void);
118
119 /*
120 * File descriptors for protocol logging when the CVS_CLIENT_LOG environment
121 * variable is set.
122 */
123 static int cvs_client_logon = 0;
124 int cvs_client_inlog_fd = -1;
125 int cvs_client_outlog_fd = -1;
126
127
128 int server_response = SERVER_OK;
129
130 static char *
client_get_supported_responses(void)131 client_get_supported_responses(void)
132 {
133 BUF *bp;
134 char *d;
135 int i, first;
136
137 first = 0;
138 bp = buf_alloc(512);
139 for (i = 0; cvs_responses[i].supported != -1; i++) {
140 if (cvs_responses[i].hdlr == NULL)
141 continue;
142
143 if (first != 0)
144 buf_putc(bp, ' ');
145 else
146 first++;
147 buf_puts(bp, cvs_responses[i].name);
148 }
149
150 buf_putc(bp, '\0');
151 d = buf_release(bp);
152 return (d);
153 }
154
155 static void
client_check_directory(char * data,char * repository)156 client_check_directory(char *data, char *repository)
157 {
158 CVSENTRIES *entlist;
159 char *entry, *parent, *base, *p;
160 char basebuf[PATH_MAX], parentbuf[PATH_MAX];
161
162 STRIP_SLASH(data);
163
164 /* first directory we get is our module root */
165 if (module_repo_root == NULL && checkout_target_dir != NULL) {
166 p = repository + strlen(current_cvsroot->cr_dir) + 1;
167 module_repo_root = xstrdup(p);
168 p = strrchr(module_repo_root, '/');
169 if (p != NULL)
170 *p = '\0';
171 }
172
173 cvs_mkpath(data, NULL);
174
175 if (cvs_cmdop == CVS_OP_EXPORT)
176 return;
177
178 if (strlcpy(basebuf, data, sizeof(basebuf)) >= sizeof(basebuf))
179 fatal("client_check_directory: truncation");
180 if ((base = basename(basebuf)) == NULL)
181 fatal("client_check_directory: overflow");
182
183 if (strlcpy(parentbuf, data, sizeof(parentbuf)) >= sizeof(parentbuf))
184 fatal("client_check_directory: truncation");
185 if ((parent = dirname(parentbuf)) == NULL)
186 fatal("client_check_directory: overflow");
187
188 if (!strcmp(parent, "."))
189 return;
190
191 entry = xmalloc(CVS_ENT_MAXLINELEN);
192 cvs_ent_line_str(base, NULL, NULL, NULL, NULL, 1, 0, entry,
193 CVS_ENT_MAXLINELEN);
194
195 entlist = cvs_ent_open(parent);
196 cvs_ent_add(entlist, entry);
197
198 free(entry);
199 }
200
201 void
cvs_client_connect_to_server(void)202 cvs_client_connect_to_server(void)
203 {
204 struct cvs_var *vp;
205 char *cmd, *argv[10], *resp;
206 int ifd[2], ofd[2], argc;
207
208 if (cvs_server_active == 1)
209 fatal("cvs_client_connect: I was already connected to server");
210
211 switch (current_cvsroot->cr_method) {
212 case CVS_METHOD_PSERVER:
213 case CVS_METHOD_KSERVER:
214 case CVS_METHOD_GSERVER:
215 case CVS_METHOD_FORK:
216 fatal("the specified connection method is not supported");
217 default:
218 break;
219 }
220
221 if (pipe(ifd) == -1)
222 fatal("cvs_client_connect: %s", strerror(errno));
223 if (pipe(ofd) == -1)
224 fatal("cvs_client_connect: %s", strerror(errno));
225
226 switch (fork()) {
227 case -1:
228 fatal("cvs_client_connect: fork failed: %s", strerror(errno));
229 case 0:
230 if (dup2(ifd[0], STDIN_FILENO) == -1)
231 fatal("cvs_client_connect: %s", strerror(errno));
232 if (dup2(ofd[1], STDOUT_FILENO) == -1)
233 fatal("cvs_client_connect: %s", strerror(errno));
234
235 close(ifd[1]);
236 close(ofd[0]);
237
238 if ((cmd = getenv("CVS_SERVER")) == NULL)
239 cmd = CVS_SERVER_DEFAULT;
240
241 argc = 0;
242 argv[argc++] = cvs_rsh;
243
244 if (current_cvsroot->cr_user != NULL) {
245 argv[argc++] = "-l";
246 argv[argc++] = current_cvsroot->cr_user;
247 }
248
249 argv[argc++] = "--";
250 argv[argc++] = current_cvsroot->cr_host;
251 argv[argc++] = cmd;
252 argv[argc++] = "server";
253 argv[argc] = NULL;
254
255 cvs_log(LP_TRACE, "connecting to server %s",
256 current_cvsroot->cr_host);
257
258 execvp(argv[0], argv);
259 fatal("cvs_client_connect: failed to execute cvs server");
260 default:
261 break;
262 }
263
264 close(ifd[0]);
265 close(ofd[1]);
266
267 if ((current_cvsroot->cr_srvin = fdopen(ifd[1], "w")) == NULL)
268 fatal("cvs_client_connect: %s", strerror(errno));
269 if ((current_cvsroot->cr_srvout = fdopen(ofd[0], "r")) == NULL)
270 fatal("cvs_client_connect: %s", strerror(errno));
271
272 setvbuf(current_cvsroot->cr_srvin, NULL,_IOLBF, 0);
273 setvbuf(current_cvsroot->cr_srvout, NULL, _IOLBF, 0);
274
275 cvs_client_initlog();
276
277 if (cvs_cmdop != CVS_OP_INIT)
278 cvs_client_send_request("Root %s", current_cvsroot->cr_dir);
279
280 resp = client_get_supported_responses();
281 cvs_client_send_request("Valid-responses %s", resp);
282 free(resp);
283
284 cvs_client_send_request("valid-requests");
285 cvs_client_get_responses();
286
287 cvs_client_send_request("UseUnchanged");
288
289 if (cvs_nolog == 1)
290 cvs_client_send_request("Global_option -l");
291
292 if (cvs_noexec == 1)
293 cvs_client_send_request("Global_option -n");
294
295 switch (verbosity) {
296 case 0:
297 cvs_client_send_request("Global_option -Q");
298 break;
299 case 1:
300 /* Be quiet. This is the default in OpenCVS. */
301 cvs_client_send_request("Global_option -q");
302 break;
303 default:
304 break;
305 }
306
307 if (cvs_readonly == 1)
308 cvs_client_send_request("Global_option -r");
309
310 if (cvs_trace == 1)
311 cvs_client_send_request("Global_option -t");
312
313 /* XXX: If 'Set' is supported? */
314 TAILQ_FOREACH(vp, &cvs_variables, cv_link)
315 cvs_client_send_request("Set %s=%s", vp->cv_name, vp->cv_val);
316 }
317
318 void
cvs_client_send_request(char * fmt,...)319 cvs_client_send_request(char *fmt, ...)
320 {
321 int i;
322 va_list ap;
323 char *data, *s;
324 struct cvs_req *req;
325
326 va_start(ap, fmt);
327 i = vasprintf(&data, fmt, ap);
328 va_end(ap);
329 if (i == -1)
330 fatal("cvs_client_send_request: could not allocate memory");
331
332 if ((s = strchr(data, ' ')) != NULL)
333 *s = '\0';
334
335 req = cvs_remote_get_request_info(data);
336 if (req == NULL)
337 fatal("'%s' is an unknown request", data);
338
339 if (req->supported != 1)
340 fatal("remote cvs server does not support '%s'", data);
341
342 if (s != NULL)
343 *s = ' ';
344
345 cvs_log(LP_TRACE, "%s", data);
346
347 cvs_remote_output(data);
348 free(data);
349 }
350
351 void
cvs_client_read_response(void)352 cvs_client_read_response(void)
353 {
354 char *cmd, *data;
355 struct cvs_resp *resp;
356
357 cmd = cvs_remote_input();
358 if ((data = strchr(cmd, ' ')) != NULL)
359 (*data++) = '\0';
360
361 resp = cvs_remote_get_response_info(cmd);
362 if (resp == NULL)
363 fatal("response '%s' is not supported by our client", cmd);
364
365 if (resp->hdlr == NULL)
366 fatal("opencvs client does not support '%s'", cmd);
367
368 (*resp->hdlr)(data);
369
370 free(cmd);
371 }
372
373 void
cvs_client_get_responses(void)374 cvs_client_get_responses(void)
375 {
376 while (end_of_response != 1)
377 cvs_client_read_response();
378
379 end_of_response = 0;
380 }
381
382 void
cvs_client_send_logmsg(char * msg)383 cvs_client_send_logmsg(char *msg)
384 {
385 char *buf, *p, *q;
386
387 (void)xasprintf(&buf, "%s\n", msg);
388
389 cvs_client_send_request("Argument -m");
390 if ((p = strchr(buf, '\n')) != NULL)
391 *p++ = '\0';
392 cvs_client_send_request("Argument %s", buf);
393 for (q = p; p != NULL; q = p) {
394 if ((p = strchr(q, '\n')) != NULL) {
395 *p++ = '\0';
396 cvs_client_send_request("Argumentx %s", q);
397 }
398 }
399
400 free(buf);
401 }
402
403 void
cvs_client_senddir(const char * dir)404 cvs_client_senddir(const char *dir)
405 {
406 struct stat st;
407 int nb;
408 char *d, *date, fpath[PATH_MAX], repo[PATH_MAX], *tag;
409
410 d = NULL;
411
412 if (lastdir != NULL && !strcmp(dir, lastdir))
413 return;
414
415 cvs_get_repository_path(dir, repo, PATH_MAX);
416
417 if (cvs_cmdop != CVS_OP_RLOG)
418 cvs_client_send_request("Directory %s\n%s", dir, repo);
419
420 (void)xsnprintf(fpath, PATH_MAX, "%s/%s",
421 dir, CVS_PATH_STATICENTRIES);
422
423 if (stat(fpath, &st) == 0 && (st.st_mode & (S_IRUSR|S_IRGRP|S_IROTH)))
424 cvs_client_send_request("Static-directory");
425
426 d = xstrdup(dir);
427 cvs_parse_tagfile(d, &tag, &date, &nb);
428
429 if (tag != NULL || date != NULL) {
430 char buf[128];
431
432 if (tag != NULL && nb != 0) {
433 if (strlcpy(buf, "N", sizeof(buf)) >= sizeof(buf))
434 fatal("cvs_client_senddir: truncation");
435 } else if (tag != NULL) {
436 if (strlcpy(buf, "T", sizeof(buf)) >= sizeof(buf))
437 fatal("cvs_client_senddir: truncation");
438 } else {
439 if (strlcpy(buf, "D", sizeof(buf)) >= sizeof(buf))
440 fatal("cvs_client_senddir: truncation");
441 }
442
443 if (strlcat(buf, tag ? tag : date, sizeof(buf)) >= sizeof(buf))
444 fatal("cvs_client_senddir: truncation");
445
446 cvs_client_send_request("Sticky %s", buf);
447
448 free(tag);
449 free(date);
450 }
451 free(d);
452 free(lastdir);
453 lastdir = xstrdup(dir);
454 }
455
456 void
cvs_client_sendfile(struct cvs_file * cf)457 cvs_client_sendfile(struct cvs_file *cf)
458 {
459 size_t len;
460 struct tm datetm;
461 char rev[CVS_REV_BUFSZ], timebuf[CVS_TIME_BUFSZ], sticky[CVS_REV_BUFSZ];
462
463 cvs_client_senddir(cf->file_wd);
464 cvs_remote_classify_file(cf);
465
466 if (cf->file_type == CVS_DIR)
467 return;
468
469 if (cf->file_ent != NULL && cvs_cmdop != CVS_OP_IMPORT) {
470 if (cf->file_status == FILE_ADDED) {
471 len = strlcpy(rev, "0", sizeof(rev));
472 if (len >= sizeof(rev))
473 fatal("cvs_client_sendfile: truncation");
474
475 len = strlcpy(timebuf, "Initial ", sizeof(timebuf));
476 if (len >= sizeof(timebuf))
477 fatal("cvs_client_sendfile: truncation");
478
479 len = strlcat(timebuf, cf->file_name, sizeof(timebuf));
480 if (len >= sizeof(timebuf))
481 fatal("cvs_client_sendfile: truncation");
482 } else {
483 rcsnum_tostr(cf->file_ent->ce_rev, rev, sizeof(rev));
484 ctime_r(&cf->file_ent->ce_mtime, timebuf);
485 }
486
487 if (cf->file_ent->ce_conflict == NULL) {
488 timebuf[strcspn(timebuf, "\n")] = '\0';
489 } else {
490 len = strlcpy(timebuf, cf->file_ent->ce_conflict,
491 sizeof(timebuf));
492 if (len >= sizeof(timebuf))
493 fatal("cvs_client_sendfile: truncation");
494 len = strlcat(timebuf, "+=", sizeof(timebuf));
495 if (len >= sizeof(timebuf))
496 fatal("cvs_client_sendfile: truncation");
497 }
498
499 sticky[0] = '\0';
500 if (cf->file_ent->ce_tag != NULL) {
501 (void)xsnprintf(sticky, sizeof(sticky), "T%s",
502 cf->file_ent->ce_tag);
503 } else if (cf->file_ent->ce_date != -1) {
504 gmtime_r(&(cf->file_ent->ce_date), &datetm);
505 (void)strftime(sticky, sizeof(sticky),
506 "D"CVS_DATE_FMT, &datetm);
507 }
508
509 cvs_client_send_request("Entry /%s/%s%s/%s/%s/%s",
510 cf->file_name, (cf->file_status == FILE_REMOVED) ? "-" : "",
511 rev, timebuf, cf->file_ent->ce_opts ?
512 cf->file_ent->ce_opts : "", sticky);
513 }
514
515 if (cvs_cmdop == CVS_OP_ADD)
516 cf->file_status = FILE_MODIFIED;
517
518 switch (cf->file_status) {
519 case FILE_UNKNOWN:
520 if (cf->file_flags & FILE_ON_DISK)
521 cvs_client_send_request("Questionable %s",
522 cf->file_name);
523 break;
524 case FILE_ADDED:
525 if (backup_local_changes) /* for update -C */
526 cvs_backup_file(cf);
527
528 cvs_client_send_request("Modified %s", cf->file_name);
529 cvs_remote_send_file(cf->file_path, cf->fd);
530 break;
531 case FILE_MODIFIED:
532 if (backup_local_changes) { /* for update -C */
533 cvs_backup_file(cf);
534 cvs_client_send_request("Entry /%s/%s%s/%s/%s/%s",
535 cf->file_name, "", rev, timebuf,
536 cf->file_ent->ce_opts ? cf->file_ent->ce_opts : "",
537 sticky);
538 break;
539 }
540
541 cvs_client_send_request("Modified %s", cf->file_name);
542 cvs_remote_send_file(cf->file_path, cf->fd);
543 break;
544 case FILE_UPTODATE:
545 cvs_client_send_request("Unchanged %s", cf->file_name);
546 break;
547 }
548 }
549
550 void
cvs_client_send_files(char ** argv,int argc)551 cvs_client_send_files(char **argv, int argc)
552 {
553 int i;
554
555 for (i = 0; i < argc; i++)
556 cvs_client_send_request("Argument %s", argv[i]);
557 }
558
559 void
cvs_client_ok(char * data)560 cvs_client_ok(char *data)
561 {
562 end_of_response = 1;
563 server_response = SERVER_OK;
564 }
565
566 void
cvs_client_error(char * data)567 cvs_client_error(char *data)
568 {
569 end_of_response = 1;
570 server_response = SERVER_ERROR;
571 }
572
573 void
cvs_client_validreq(char * data)574 cvs_client_validreq(char *data)
575 {
576 int i;
577 char *sp, *ep;
578 struct cvs_req *req;
579
580 if ((sp = data) == NULL)
581 fatal("Missing argument for Valid-requests");
582
583 do {
584 if ((ep = strchr(sp, ' ')) != NULL)
585 *ep = '\0';
586
587 req = cvs_remote_get_request_info(sp);
588 if (req != NULL)
589 req->supported = 1;
590
591 if (ep != NULL)
592 sp = ep + 1;
593 } while (ep != NULL);
594
595 for (i = 0; cvs_requests[i].supported != -1; i++) {
596 req = &cvs_requests[i];
597 if ((req->flags & REQ_NEEDED) &&
598 req->supported != 1) {
599 fatal("server does not support required '%s'",
600 req->name);
601 }
602 }
603 }
604
605 void
cvs_client_e(char * data)606 cvs_client_e(char *data)
607 {
608 if (data == NULL)
609 fatal("Missing argument for E");
610
611 fprintf(stderr, "%s\n", data);
612 }
613
614 void
cvs_client_m(char * data)615 cvs_client_m(char *data)
616 {
617 if (data == NULL)
618 fatal("Missing argument for M");
619
620 puts(data);
621 }
622
623 void
cvs_client_checkedin(char * data)624 cvs_client_checkedin(char *data)
625 {
626 CVSENTRIES *entlist;
627 struct cvs_ent *ent, *newent;
628 size_t len;
629 struct tm datetm;
630 char *dir, *e, *entry, rev[CVS_REV_BUFSZ];
631 char sticky[CVS_ENT_MAXLINELEN], timebuf[CVS_TIME_BUFSZ];
632
633 if (data == NULL)
634 fatal("Missing argument for Checked-in");
635
636 dir = cvs_remote_input();
637 e = cvs_remote_input();
638 free(dir);
639
640 entlist = cvs_ent_open(data);
641 newent = cvs_ent_parse(e);
642 ent = cvs_ent_get(entlist, newent->ce_name);
643 free(e);
644
645 rcsnum_tostr(newent->ce_rev, rev, sizeof(rev));
646
647 sticky[0] = '\0';
648 if (ent == NULL) {
649 len = strlcpy(rev, "0", sizeof(rev));
650 if (len >= sizeof(rev))
651 fatal("cvs_client_sendfile: truncation");
652
653 len = strlcpy(timebuf, "Initial ", sizeof(timebuf));
654 if (len >= sizeof(timebuf))
655 fatal("cvs_client_sendfile: truncation");
656
657 len = strlcat(timebuf, newent->ce_name, sizeof(timebuf));
658 if (len >= sizeof(timebuf))
659 fatal("cvs_client_sendfile: truncation");
660 } else {
661 gmtime_r(&ent->ce_mtime, &datetm);
662 asctime_r(&datetm, timebuf);
663 timebuf[strcspn(timebuf, "\n")] = '\0';
664
665 if (newent->ce_tag != NULL) {
666 (void)xsnprintf(sticky, sizeof(sticky), "T%s",
667 newent->ce_tag);
668 } else if (newent->ce_date != -1) {
669 gmtime_r(&(newent->ce_date), &datetm);
670 (void)strftime(sticky, sizeof(sticky),
671 "D"CVS_DATE_FMT, &datetm);
672 }
673
674 cvs_ent_free(ent);
675 }
676
677 entry = xmalloc(CVS_ENT_MAXLINELEN);
678 cvs_ent_line_str(newent->ce_name, rev, timebuf,
679 newent->ce_opts ? newent->ce_opts : "", sticky, 0,
680 newent->ce_status == CVS_ENT_REMOVED ? 1 : 0,
681 entry, CVS_ENT_MAXLINELEN);
682
683 cvs_ent_free(newent);
684 cvs_ent_add(entlist, entry);
685
686 free(entry);
687 }
688
689 void
cvs_client_updated(char * data)690 cvs_client_updated(char *data)
691 {
692 int fd;
693 time_t now;
694 mode_t fmode;
695 size_t flen;
696 CVSENTRIES *ent;
697 struct cvs_ent *e;
698 const char *errstr;
699 struct tm datetm;
700 struct timeval tv[2];
701 char repo[PATH_MAX], *entry;
702 char timebuf[CVS_TIME_BUFSZ], revbuf[CVS_REV_BUFSZ];
703 char *en, *mode, *len, *rpath, *p;
704 char sticky[CVS_ENT_MAXLINELEN], fpath[PATH_MAX];
705
706 if (data == NULL)
707 fatal("Missing argument for Updated");
708
709 rpath = cvs_remote_input();
710 en = cvs_remote_input();
711 mode = cvs_remote_input();
712 len = cvs_remote_input();
713
714 client_check_directory(data, rpath);
715 cvs_get_repository_path(".", repo, PATH_MAX);
716
717 STRIP_SLASH(repo);
718
719 if (strlen(repo) + 1 > strlen(rpath))
720 fatal("received a repository path that is too short");
721
722 p = strrchr(rpath, '/');
723 if (p == NULL)
724 fatal("malicious repository path from server");
725
726 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", data, p);
727
728 flen = strtonum(len, 0, INT_MAX, &errstr);
729 if (errstr != NULL)
730 fatal("cvs_client_updated: %s: %s", len, errstr);
731 free(len);
732
733 cvs_strtomode(mode, &fmode);
734 free(mode);
735 fmode &= ~cvs_umask;
736
737 time(&now);
738 gmtime_r(&now, &datetm);
739 asctime_r(&datetm, timebuf);
740 timebuf[strcspn(timebuf, "\n")] = '\0';
741
742 e = cvs_ent_parse(en);
743 free(en);
744
745 sticky[0] = '\0';
746 if (e->ce_tag != NULL) {
747 (void)xsnprintf(sticky, sizeof(sticky), "T%s", e->ce_tag);
748 } else if (e->ce_date != -1) {
749 gmtime_r(&(e->ce_date), &datetm);
750 (void)strftime(sticky, sizeof(sticky),
751 "D"CVS_DATE_FMT, &datetm);
752 }
753
754 rcsnum_tostr(e->ce_rev, revbuf, sizeof(revbuf));
755
756 entry = xmalloc(CVS_ENT_MAXLINELEN);
757 cvs_ent_line_str(e->ce_name, revbuf, timebuf,
758 e->ce_opts ? e->ce_opts : "", sticky, 0, 0,
759 entry, CVS_ENT_MAXLINELEN);
760
761 cvs_ent_free(e);
762
763 if (cvs_cmdop != CVS_OP_EXPORT) {
764 ent = cvs_ent_open(data);
765 cvs_ent_add(ent, entry);
766 }
767
768 free(entry);
769
770 (void)unlink(fpath);
771 if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
772 fatal("cvs_client_updated: open: %s: %s",
773 fpath, strerror(errno));
774
775 cvs_remote_receive_file(fd, flen);
776
777 tv[0].tv_sec = now;
778 tv[0].tv_usec = 0;
779 tv[1] = tv[0];
780
781 if (futimes(fd, tv) == -1)
782 fatal("cvs_client_updated: futimes: %s", strerror(errno));
783
784 if (fchmod(fd, fmode) == -1)
785 fatal("cvs_client_updated: fchmod: %s", strerror(errno));
786
787 (void)close(fd);
788
789 free(rpath);
790 }
791
792 void
cvs_client_merged(char * data)793 cvs_client_merged(char *data)
794 {
795 int fd;
796 time_t now;
797 mode_t fmode;
798 size_t flen;
799 CVSENTRIES *ent;
800 const char *errstr;
801 struct timeval tv[2];
802 struct tm datetm;
803 char timebuf[CVS_TIME_BUFSZ], *repo, *rpath, *entry, *mode;
804 char *len, *fpath, *wdir, wdirbuf[PATH_MAX];
805
806 if (data == NULL)
807 fatal("Missing argument for Merged");
808
809 rpath = cvs_remote_input();
810 entry = cvs_remote_input();
811 mode = cvs_remote_input();
812 len = cvs_remote_input();
813
814 client_check_directory(data, rpath);
815
816 repo = xmalloc(PATH_MAX);
817 cvs_get_repository_path(".", repo, PATH_MAX);
818
819 STRIP_SLASH(repo);
820
821 if (strlen(repo) + 1 > strlen(rpath))
822 fatal("received a repository path that is too short");
823
824 fpath = rpath + strlen(repo) + 1;
825 if (strlcpy(wdirbuf, fpath, sizeof(wdirbuf)) >= sizeof(wdirbuf))
826 fatal("cvs_client_merged: truncation");
827 if ((wdir = dirname(wdirbuf)) == NULL)
828 fatal("cvs_client_merged: dirname: %s", strerror(errno));
829 free(repo);
830
831 flen = strtonum(len, 0, INT_MAX, &errstr);
832 if (errstr != NULL)
833 fatal("cvs_client_merged: %s: %s", len, errstr);
834 free(len);
835
836 cvs_strtomode(mode, &fmode);
837 free(mode);
838 fmode &= ~cvs_umask;
839
840 time(&now);
841 gmtime_r(&now, &datetm);
842 asctime_r(&datetm, timebuf);
843 timebuf[strcspn(timebuf, "\n")] = '\0';
844
845 ent = cvs_ent_open(wdir);
846 cvs_ent_add(ent, entry);
847 free(entry);
848
849 (void)unlink(fpath);
850 if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
851 fatal("cvs_client_merged: open: %s: %s",
852 fpath, strerror(errno));
853
854 cvs_remote_receive_file(fd, flen);
855
856 tv[0].tv_sec = now;
857 tv[0].tv_usec = 0;
858 tv[1] = tv[0];
859
860 if (futimes(fd, tv) == -1)
861 fatal("cvs_client_merged: futimes: %s", strerror(errno));
862
863 if (fchmod(fd, fmode) == -1)
864 fatal("cvs_client_merged: fchmod: %s", strerror(errno));
865
866 (void)close(fd);
867
868 free(rpath);
869 }
870
871 void
cvs_client_removed(char * data)872 cvs_client_removed(char *data)
873 {
874 CVSENTRIES *entlist;
875 char *rpath, *filename, fpath[PATH_MAX];
876
877 if (data == NULL)
878 fatal("Missing argument for Removed");
879
880 rpath = cvs_remote_input();
881 if ((filename = strrchr(rpath, '/')) == NULL)
882 fatal("bad rpath in cvs_client_removed: %s", rpath);
883 filename++;
884
885 entlist = cvs_ent_open(data);
886 cvs_ent_remove(entlist, filename);
887
888 (void)xsnprintf(fpath, PATH_MAX, "%s/%s", data, filename);
889 (void)unlink(fpath);
890
891 free(rpath);
892 }
893
894 void
cvs_client_remove_entry(char * data)895 cvs_client_remove_entry(char *data)
896 {
897 CVSENTRIES *entlist;
898 char *filename, *rpath;
899
900 if (data == NULL)
901 fatal("Missing argument for Remove-entry");
902
903 rpath = cvs_remote_input();
904 if ((filename = strrchr(rpath, '/')) == NULL)
905 fatal("bad rpath in cvs_client_remove_entry: %s", rpath);
906 filename++;
907
908 entlist = cvs_ent_open(data);
909 cvs_ent_remove(entlist, filename);
910
911 free(rpath);
912 }
913
914 void
cvs_client_set_static_directory(char * data)915 cvs_client_set_static_directory(char *data)
916 {
917 FILE *fp;
918 char *dir, fpath[PATH_MAX];
919
920 if (data == NULL)
921 fatal("Missing argument for Set-static-directory");
922
923 STRIP_SLASH(data);
924
925 dir = cvs_remote_input();
926 free(dir);
927
928 if (cvs_cmdop == CVS_OP_EXPORT)
929 return;
930
931 (void)xsnprintf(fpath, PATH_MAX, "%s/%s",
932 data, CVS_PATH_STATICENTRIES);
933
934 if ((fp = fopen(fpath, "w+")) == NULL) {
935 cvs_log(LP_ERRNO, "%s", fpath);
936 return;
937 }
938 (void)fclose(fp);
939 }
940
941 void
cvs_client_clear_static_directory(char * data)942 cvs_client_clear_static_directory(char *data)
943 {
944 char *dir, fpath[PATH_MAX];
945
946 if (data == NULL)
947 fatal("Missing argument for Clear-static-directory");
948
949 STRIP_SLASH(data);
950
951 dir = cvs_remote_input();
952 free(dir);
953
954 if (cvs_cmdop == CVS_OP_EXPORT)
955 return;
956
957 (void)xsnprintf(fpath, PATH_MAX, "%s/%s",
958 data, CVS_PATH_STATICENTRIES);
959
960 (void)cvs_unlink(fpath);
961 }
962
963 void
cvs_client_set_sticky(char * data)964 cvs_client_set_sticky(char *data)
965 {
966 FILE *fp;
967 char *dir, *tag, tagpath[PATH_MAX];
968
969 if (data == NULL)
970 fatal("Missing argument for Set-sticky");
971
972 STRIP_SLASH(data);
973
974 dir = cvs_remote_input();
975 tag = cvs_remote_input();
976
977 if (cvs_cmdop == CVS_OP_EXPORT)
978 goto out;
979
980 client_check_directory(data, dir);
981
982 (void)xsnprintf(tagpath, PATH_MAX, "%s/%s", data, CVS_PATH_TAG);
983
984 if ((fp = fopen(tagpath, "w+")) == NULL) {
985 cvs_log(LP_ERRNO, "%s", tagpath);
986 goto out;
987 }
988
989 (void)fprintf(fp, "%s\n", tag);
990 (void)fclose(fp);
991 out:
992 free(tag);
993 free(dir);
994 }
995
996 void
cvs_client_clear_sticky(char * data)997 cvs_client_clear_sticky(char *data)
998 {
999 char *dir, tagpath[PATH_MAX];
1000
1001 if (data == NULL)
1002 fatal("Missing argument for Clear-sticky");
1003
1004 STRIP_SLASH(data);
1005
1006 dir = cvs_remote_input();
1007
1008 if (cvs_cmdop == CVS_OP_EXPORT) {
1009 free(dir);
1010 return;
1011 }
1012
1013 client_check_directory(data, dir);
1014
1015 (void)xsnprintf(tagpath, PATH_MAX, "%s/%s", data, CVS_PATH_TAG);
1016 (void)unlink(tagpath);
1017
1018 free(dir);
1019 }
1020
1021
1022 /*
1023 * cvs_client_initlog()
1024 *
1025 * Initialize protocol logging if the CVS_CLIENT_LOG environment variable is
1026 * set. In this case, the variable's value is used as a path to which the
1027 * appropriate suffix is added (".in" for client input and ".out" for server
1028 * output).
1029 */
1030 static void
cvs_client_initlog(void)1031 cvs_client_initlog(void)
1032 {
1033 u_int i;
1034 char *env, *envdup, buf[PATH_MAX], fpath[PATH_MAX];
1035 char rpath[PATH_MAX], timebuf[CVS_TIME_BUFSZ], *s;
1036 struct stat st;
1037 time_t now;
1038 struct passwd *pwd;
1039
1040 /* avoid doing it more than once */
1041 if (cvs_client_logon)
1042 return;
1043
1044 if ((env = getenv("CVS_CLIENT_LOG")) == NULL)
1045 return;
1046
1047 envdup = xstrdup(env);
1048 if ((s = strchr(envdup, '%')) != NULL)
1049 *s = '\0';
1050
1051 if (strlcpy(buf, env, sizeof(buf)) >= sizeof(buf))
1052 fatal("cvs_client_initlog: truncation");
1053
1054 if (strlcpy(rpath, envdup, sizeof(rpath)) >= sizeof(rpath))
1055 fatal("cvs_client_initlog: truncation");
1056
1057 free(envdup);
1058
1059 s = buf;
1060 while ((s = strchr(s, '%')) != NULL) {
1061 s++;
1062 switch (*s) {
1063 case 'c':
1064 if (strlcpy(fpath, cmdp->cmd_name, sizeof(fpath)) >=
1065 sizeof(fpath))
1066 fatal("cvs_client_initlog: truncation");
1067 break;
1068 case 'd':
1069 time(&now);
1070 ctime_r(&now, timebuf);
1071 timebuf[strcspn(timebuf, "\n")] = '\0';
1072 if (strlcpy(fpath, timebuf, sizeof(fpath)) >=
1073 sizeof(fpath))
1074 fatal("cvs_client_initlog: truncation");
1075 break;
1076 case 'p':
1077 (void)xsnprintf(fpath, sizeof(fpath), "%d", getpid());
1078 break;
1079 case 'u':
1080 if ((pwd = getpwuid(getuid())) != NULL) {
1081 if (strlcpy(fpath, pwd->pw_name,
1082 sizeof(fpath)) >= sizeof(fpath))
1083 fatal("cvs_client_initlog: truncation");
1084 } else {
1085 fpath[0] = '\0';
1086 }
1087 endpwent();
1088 break;
1089 default:
1090 fpath[0] = '\0';
1091 break;
1092 }
1093
1094 if (fpath[0] != '\0') {
1095 if (strlcat(rpath, "-", sizeof(rpath)) >= sizeof(rpath))
1096 fatal("cvs_client_initlog: truncation");
1097
1098 if (strlcat(rpath, fpath, sizeof(rpath))
1099 >= sizeof(rpath))
1100 fatal("cvs_client_initlog: truncation");
1101 }
1102 }
1103
1104 for (i = 0; i < UINT_MAX; i++) {
1105 (void)xsnprintf(fpath, sizeof(fpath), "%s-%d.in", rpath, i);
1106
1107 if (stat(fpath, &st) != -1)
1108 continue;
1109
1110 if (errno != ENOENT)
1111 fatal("cvs_client_initlog() stat failed '%s'",
1112 strerror(errno));
1113
1114 break;
1115 }
1116
1117 if ((cvs_client_inlog_fd = open(fpath,
1118 O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1) {
1119 fatal("cvs_client_initlog: open `%s': %s",
1120 fpath, strerror(errno));
1121 }
1122
1123 for (i = 0; i < UINT_MAX; i++) {
1124 (void)xsnprintf(fpath, sizeof(fpath), "%s-%d.out", rpath, i);
1125
1126 if (stat(fpath, &st) != -1)
1127 continue;
1128
1129 if (errno != ENOENT)
1130 fatal("cvs_client_initlog() stat failed '%s'",
1131 strerror(errno));
1132
1133 break;
1134 }
1135
1136 if ((cvs_client_outlog_fd = open(fpath,
1137 O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1) {
1138 fatal("cvs_client_initlog: open `%s': %s",
1139 fpath, strerror(errno));
1140 }
1141
1142 cvs_client_logon = 1;
1143 }
1144