Merge branch master into rorcz
[girocco/readme.git] / src / peek_packet.c
blob1e43142f8fdb498e54d30ca5b2484c2cc9eac771
1 /*
3 peek_packet.c -- peek_packet utility to peek at incoming git-daemon request
4 Copyright (C) 2015 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 This utility is intended to be used by a script front end to git daemon
24 running in inetd mode. The first thing the script does is call this utility
25 which attempts to peek the first incoming Git packet off the connection
26 and then output contents after the initial 4-character hex length upto but
27 excluding the first \0 character.
29 At that point the script can validate the incoming request and if it chooses
30 to allow it then exec git daemon to process it. Since the packet was peeked
31 it's still there to be read by git daemon.
33 Note that there is a hard-coded timeout of 30 seconds and a hard-coded limit
34 of PATH_MAX for the length of the initial packet.
36 On failure a non-zero exit code is returned. On success a 0 exit code is
37 returned and peeked text is output to stdout.
39 The connection to be peeked must be fd 0 and will have SO_KEEPALIVE set on it.
41 This utility does not take any arguments and ignores any that are given.
43 The output of a successful peek should be one of these:
45 git-upload-pack /<...>
46 git-upload-archive /<...>
47 git-receive-pack /<...>
49 where "<...>" is replaced with the repository path so, for example, doing
50 "git ls-remote git://example.com/foo.git" would result in this output:
52 git-upload-pack /foo.git
54 Note that the first character could be a ~ instead of a /, but it's
55 probably best to reject those.
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 #include <limits.h>
63 #include <time.h>
64 #include <signal.h>
65 #include <sys/types.h>
66 #include <sys/socket.h>
68 /* Note that mod_reqtimeout has a default configuration of 20 seconds
69 * maximum to wait for the first byte of the initial request line and
70 * then no more than 40 seconds total, but after the first byte is
71 * received the rest must arrive at 500 bytes/sec or faster. That
72 * means 10000 bytes minimum in 40 seconds. We do not allow the
73 * initial Git packet to be longer than PATH_MAX (which is typically
74 * either 1024 or 4096). And since 20 + 1024/500 = 22.048 and
75 * 20 + 4096/500 = 28.192 using 30 seconds for a total timeout is
76 * quite reasonable in comparison to mod_reqtimeout's default conf.
79 #define TIMEOUT_SECS 30 /* no more than 30 seconds for initial packet */
81 #define POLL_QUANTUM 100000U /* how often to poll in microseconds */
83 static int xdig(char c)
85 if ('0' <= c && c <= '9')
86 return c - '0';
87 if ('a' <= c && c <= 'f')
88 return c - 'a' + 10;
89 if ('A' <= c && c <= 'F')
90 return c - 'A' + 10;
91 return -1;
94 static char buffer[PATH_MAX];
95 static time_t expiry;
97 /* Ideally we could just use MSG_PEEK + MSG_WAITALL, and that works nicely
98 * on BSD-type distros. Unfortunately very bad things happen on Linux with
99 * that combination -- a CPU core runs at 100% until all the data arrives.
100 * So instead we omit the MSG_WAITALL and poll every POLL_QUANTUM interval
101 * to see if we've satisfied the requested amount yet.
103 static int recv_peekall(int fd, void *buff, size_t len)
105 int ans;
106 while ((ans = recv(fd, buff, len, MSG_PEEK)) > 0 && (size_t)ans < len) {
107 if (time(NULL) > expiry)
108 exit(2);
109 usleep(POLL_QUANTUM);
111 return ans < 0 ? -1 : (int)len;
114 static void handle_sigalrm(int s)
116 (void)s;
117 _exit(2);
120 static void clear_alarm(void)
122 alarm(0);
125 int main(int argc, char *argv[])
127 int len;
128 int xvals[4];
129 char hexlen[4];
130 size_t pktlen;
131 const char *nullptr;
132 int optval;
134 (void)argc;
135 (void)argv;
137 /* Ideally calling recv with MSG_PEEK would never, ever hang. However
138 * even with MSG_PEEK, recv still waits for at least the first message
139 * to arrive on the socket (unless it's non-blocking). For this reason
140 * we set an alarm timer at TIMEOUT_SECS + 2 to make sure we don't
141 * remain stuck in the recv call waiting for the first message.
143 signal(SIGALRM, handle_sigalrm);
144 alarm(TIMEOUT_SECS + 2); /* Some slop as this shouldn't be needed */
145 atexit(clear_alarm); /* Probably not necessary, but do it anyway */
147 expiry = time(NULL) + TIMEOUT_SECS;
149 optval = 1;
150 if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)))
151 return 1;
153 len = recv_peekall(0, hexlen, 4);
154 if (len != 4)
155 return 1;
157 if ((xvals[0]=xdig(hexlen[0])) < 0 ||
158 (xvals[1]=xdig(hexlen[1])) < 0 ||
159 (xvals[2]=xdig(hexlen[2])) < 0 ||
160 (xvals[3]=xdig(hexlen[3])) < 0)
161 return 1;
162 pktlen = ((unsigned)xvals[0] << 12) |
163 ((unsigned)xvals[1] << 8) |
164 ((unsigned)xvals[2] << 4) |
165 (unsigned)xvals[3];
166 if (pktlen < 22 || pktlen > sizeof(buffer))
167 return 1;
169 len = recv_peekall(0, buffer, pktlen);
170 if (len != (int)pktlen)
171 return 1;
173 if (memcmp(buffer+4, "git-", 4)) /* sanity check */
174 return 1;
175 nullptr = (const char *)memchr(buffer+4, 0, pktlen-4);
176 if (!nullptr || nullptr < (buffer+21))
177 return 1;
178 puts(buffer + 4);
180 return 0;