aboutsummaryrefslogtreecommitdiff
path: root/daemon.c
blob: 315a74bf108dc648fd9b86b40a4257fe27df907e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include "cache.h"
#include "pkt-line.h"
#include <sys/socket.h>
#include <netinet/in.h>

static const char daemon_usage[] = "git-daemon [--port=n]";

static int upload(char *dir, int dirlen)
{
	if (chdir(dir) < 0)
		return -1;
	chdir(".git");

	/*
	 * Security on the cheap.
	 *
	 * We want a readable HEAD, usable "objects" directory, and 
	 * a "git-daemon-export-ok" flag that says that the other side
	 * is ok with us doing this.
	 */
	if (access("git-daemon-export-ok", F_OK) ||
	    access("objects/00", X_OK) ||
	    access("HEAD", R_OK))
		return -1;

	/* git-upload-pack only ever reads stuff, so this is safe */
	execlp("git-upload-pack", "git-upload-pack", ".", NULL);
	return -1;
}

static int execute(char *line, int len)
{
	if (!strncmp("git-upload-pack /", line, 17))
		return upload(line + 16, len - 16);

	fprintf(stderr, "got bad connection '%s'\n", line);
	return -1;
}

static void handle(int incoming, struct sockaddr_in *addr, int addrlen)
{
	static char line[1000];
	int len;

	if (fork()) {
		close(incoming);
		return;
	}

	dup2(incoming, 0);
	dup2(incoming, 1);
	close(incoming);
	len = packet_read_line(0, line, sizeof(line));

	if (len && line[len-1] == '\n')
		line[--len] = 0;

	exit(execute(line, len));
}

static int serve(int port)
{
	int sockfd;
	struct sockaddr_in addr;

	sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
	if (sockfd < 0)
		die("unable to open socket (%s)", strerror(errno));
	memset(&addr, 0, sizeof(addr));
	addr.sin_port = htons(port);
	addr.sin_family = AF_INET;
	if (bind(sockfd, (void *)&addr, sizeof(addr)) < 0)
		die("unable to bind to port %d (%s)", port, strerror(errno));
	if (listen(sockfd, 5) < 0)
		die("unable to listen to port %d (%s)", port, strerror(errno));

	for (;;) {
		struct sockaddr_in in;
		socklen_t addrlen = sizeof(in);
		int incoming = accept(sockfd, (void *)&in, &addrlen);

		if (incoming < 0) {
			switch (errno) {
			case EAGAIN:
			case EINTR:
			case ECONNABORTED:
				continue;
			default:
				die("accept returned %s", strerror(errno));
			}
		}
		handle(incoming, &in, addrlen);
	}
}

int main(int argc, char **argv)
{
	int port = DEFAULT_GIT_PORT;
	int i;

	for (i = 1; i < argc; i++) {
		char *arg = argv[i];

		if (!strncmp(arg, "--port=", 7)) {
			char *end;
			unsigned long n;
			n = strtoul(arg+7, &end, 0);
			if (arg[7] && !*end) {
				port = n;
				continue;
			}
		}
		usage(daemon_usage);
	}

	return serve(port);
}