2 // Functions that handle communication with a Citadel Server
4 // Copyright (c) 1987-2018 by the citadel.org team
6 // This program is open source software. It runs great on the
7 // Linux operating system (and probably elsewhere). You can use,
8 // copy, and run it under the terms of the GNU General Public
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.
18 struct ctdlsession *cpool = NULL; // linked list of connections to the Citadel server
19 pthread_mutex_t cpool_mutex = PTHREAD_MUTEX_INITIALIZER; // Lock it before modifying
23 * Read a specific number of bytes of binary data from the Citadel server.
24 * Returns the number of bytes read or -1 for error.
26 int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested)
31 while (bytes_read < bytes_requested) {
32 c = read(ctdl->sock, &buf[bytes_read], bytes_requested-bytes_read);
34 syslog(LOG_DEBUG, "Socket error or zero-length read");
44 * Read a newline-terminated line of text from the Citadel server.
45 * Returns the string length or -1 for error.
47 int ctdl_readline(struct ctdlsession *ctdl, char *buf, int maxbytes)
55 while (len < maxbytes) {
56 c = read(ctdl->sock, &buf[len], 1);
58 syslog(LOG_DEBUG, "Socket error or zero-length read");
61 if (buf[len] == '\n') {
62 if ((len > 0) && (buf[len - 1] == '\r')) {
66 // syslog(LOG_DEBUG, "\033[33m[ %s\033[0m", buf);
71 // syslog(LOG_DEBUG, "\033[33m[ %s\033[0m", buf);
77 * Read lines of text from the Citadel server until a 000 terminator is received.
78 * Implemented in terms of ctdl_readline() and is therefore transparent...
79 * Returns a newly allocated StrBuf or NULL for error.
81 StrBuf *ctdl_readtextmsg(struct ctdlsession * ctdl)
84 StrBuf *sj = NewStrBuf();
89 while ((ctdl_readline(ctdl, buf, sizeof(buf)) >= 0) && (strcmp(buf, "000"))) {
90 StrBufAppendPrintf(sj, "%s\n", buf);
98 * Write to the Citadel server. For now we're just wrapping write() in case we
99 * need to add anything else later.
101 ssize_t ctdl_write(struct ctdlsession * ctdl, const void *buf, size_t count)
103 return write(ctdl->sock, buf, count);
108 * printf() type function to send data to the Citadel Server.
110 void ctdl_printf(struct ctdlsession *ctdl, const char *format, ...)
113 StrBuf *Buf = NewStrBuf();
115 va_start(arg_ptr, format);
116 StrBufVAppendPrintf(Buf, format, arg_ptr);
119 syslog(LOG_DEBUG, "\033[32m] %s\033[0m", ChrPtr(Buf));
120 ctdl_write(ctdl, (char *) ChrPtr(Buf), StrLength(Buf));
121 ctdl_write(ctdl, "\n", 1);
127 * Client side - connect to a unix domain socket
129 int uds_connectsock(char *sockpath)
131 struct sockaddr_un addr;
134 memset(&addr, 0, sizeof(addr));
135 addr.sun_family = AF_UNIX;
136 strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
138 s = socket(AF_UNIX, SOCK_STREAM, 0);
140 syslog(LOG_WARNING, "Can't create socket [%s]: %s", sockpath, strerror(errno));
144 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
145 syslog(LOG_WARNING, "Can't connect [%s]: %s", sockpath, strerror(errno));
154 * TCP client - connect to a host/port
156 int tcp_connectsock(char *host, char *service)
158 struct in6_addr serveraddr;
159 struct addrinfo hints;
160 struct addrinfo *res = NULL;
161 struct addrinfo *ai = NULL;
165 if ((host == NULL) || IsEmptyStr(host))
167 if ((service == NULL) || IsEmptyStr(service))
170 syslog(LOG_DEBUG, "tcp_connectsock(%s,%s)", host, service);
172 memset(&hints, 0x00, sizeof(hints));
173 hints.ai_flags = AI_NUMERICSERV;
174 hints.ai_family = AF_UNSPEC;
175 hints.ai_socktype = SOCK_STREAM;
178 * Handle numeric IPv4 and IPv6 addresses
180 rc = inet_pton(AF_INET, host, &serveraddr);
181 if (rc == 1) { /* dotted quad */
182 hints.ai_family = AF_INET;
183 hints.ai_flags |= AI_NUMERICHOST;
185 rc = inet_pton(AF_INET6, host, &serveraddr);
186 if (rc == 1) { /* IPv6 address */
187 hints.ai_family = AF_INET6;
188 hints.ai_flags |= AI_NUMERICHOST;
192 /* Begin the connection process */
194 rc = getaddrinfo(host, service, &hints, &res);
196 syslog(LOG_DEBUG, "%s: %s", host, gai_strerror(rc));
202 * Try all available addresses until we connect to one or until we run out.
204 for (ai = res; ai != NULL; ai = ai->ai_next) {
206 if (ai->ai_family == AF_INET)
207 syslog(LOG_DEBUG, "Trying IPv4");
208 else if (ai->ai_family == AF_INET6)
209 syslog(LOG_DEBUG, "Trying IPv6");
211 syslog(LOG_WARNING, "This is going to fail.");
213 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
215 syslog(LOG_WARNING, "socket() failed: %s", strerror(errno));
219 rc = connect(s, ai->ai_addr, ai->ai_addrlen);
224 fdflags = fcntl(rc, F_GETFL);
226 syslog(LOG_ERR, "unable to get socket %d flags! %s", rc, strerror(errno));
230 fdflags = fdflags | O_NONBLOCK;
231 if (fcntl(rc, F_SETFL, fdflags) < 0) {
232 syslog(LOG_ERR, "unable to set socket %d nonblocking flags! %s", rc, strerror(errno));
239 syslog(LOG_WARNING, "connect() failed: %s", strerror(errno));
249 * Extract from the headers, the username and password the client is attempting to use.
250 * This could be HTTP AUTH or it could be in the cookies.
252 void extract_auth(struct http_transaction *h, char *authbuf, int authbuflen)
258 char *authheader = header_val(h, "Authorization");
260 if (!strncasecmp(authheader, "Basic ", 6)) {
261 safestrncpy(authbuf, &authheader[6], authbuflen);
262 return; // HTTP-AUTH was found -- stop here
266 char *cookieheader = header_val(h, "Cookie");
268 char *wcauth = strstr(cookieheader, "wcauth=");
270 safestrncpy(authbuf, &cookieheader[7], authbuflen);
271 char *semicolon = strchr(authbuf, ';');
272 if (semicolon != NULL) {
275 if (strlen(authbuf) < 3) { // impossibly small
278 return; // Cookie auth was found -- stop here
281 // no authorization found in headers ... this is an anonymous session
286 * Log in to the Citadel server. Returns 0 on success or nonzero on error.
288 * 'auth' should be a base64-encoded "username:password" combination (like in http-auth)
290 * If 'resultbuf' is not NULL, it should be a buffer of at least 1024 characters,
291 * and will be filled with the result from a Citadel server command.
293 int login_to_citadel(struct ctdlsession *c, char *auth, char *resultbuf)
298 char supplied_username[AUTH_MAX];
299 char supplied_password[AUTH_MAX];
301 if (resultbuf != NULL) {
307 buflen = CtdlDecodeBase64(buf, auth, strlen(auth));
308 extract_token(supplied_username, buf, 0, ':', sizeof supplied_username);
309 extract_token(supplied_password, buf, 1, ':', sizeof supplied_password);
310 syslog(LOG_DEBUG, "Supplied credentials: username=%s, pwlen=%d", supplied_username, (int) strlen(supplied_password));
312 ctdl_printf(c, "USER %s", supplied_username);
313 ctdl_readline(c, buf, 1024);
315 syslog(LOG_DEBUG, "No such user: %s", buf);
316 return (1); // no such user; resultbuf will explain why
319 ctdl_printf(c, "PASS %s", supplied_password);
320 ctdl_readline(c, buf, 1024);
323 strcpy(c->auth, auth);
324 extract_token(c->whoami, &buf[4], 0, '|', sizeof c->whoami);
325 syslog(LOG_DEBUG, "Login succeeded: %s", buf);
329 syslog(LOG_DEBUG, "Login failed: %s", buf);
330 return (1); // login failed; resultbuf will explain why
335 * Hunt for, or create, a connection to our Citadel Server
337 struct ctdlsession *connect_to_citadel(struct http_transaction *h)
339 struct ctdlsession *cptr = NULL;
340 struct ctdlsession *my_session = NULL;
341 int is_new_session = 0;
346 // Does the request carry a username and password?
347 extract_auth(h, auth, sizeof auth);
348 syslog(LOG_DEBUG, "Session auth: %s", auth); // remove this log when development is done
350 // Lock the connection pool while we claim our connection
351 pthread_mutex_lock(&cpool_mutex);
353 for (cptr = cpool; ((cptr != NULL) && (my_session == NULL)); cptr = cptr->next) {
354 if ((cptr->is_bound == 0) && (!strcmp(cptr->auth, auth))) {
356 my_session->is_bound = 1;
359 if (my_session == NULL) {
360 syslog(LOG_DEBUG, "No qualifying sessions , starting a new one");
361 my_session = malloc(sizeof(struct ctdlsession));
362 if (my_session != NULL) {
363 memset(my_session, 0, sizeof(struct ctdlsession));
365 my_session->next = cpool;
367 my_session->is_bound = 1;
370 pthread_mutex_unlock(&cpool_mutex);
371 if (my_session == NULL) {
372 return (NULL); // oh well
375 if (my_session->sock < 3) {
377 } else { // make sure our Citadel session is still good
379 test_conn = ctdl_write(my_session, HKEY("NOOP\n"));
381 syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
382 close(my_session->sock);
383 my_session->sock = 0;
386 test_conn = ctdl_readline(my_session, buf, sizeof(buf));
388 syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
389 close(my_session->sock);
390 my_session->sock = 0;
396 if (is_new_session) {
397 strcpy(my_session->room, "");
398 my_session->sock = tcp_connectsock(ctdlhost, ctdlport);
399 ctdl_readline(my_session, buf, sizeof(buf)); // skip past the server greeting banner
401 if (!IsEmptyStr(auth)) { // do we need to log in to Citadel?
402 r = login_to_citadel(my_session, auth, NULL); // FIXME figure out what happens if login failed
405 ctdl_printf(my_session, "NOOP");
406 ctdl_readline(my_session, buf, sizeof(buf));
407 my_session->last_access = time(NULL);
408 ++my_session->num_requests_handled;
415 * Release our Citadel Server connection back into the pool.
417 void disconnect_from_citadel(struct ctdlsession *ctdl)
419 pthread_mutex_lock(&cpool_mutex);
421 pthread_mutex_unlock(&cpool_mutex);