1 // Functions that handle communication with a Citadel Server
3 // Copyright (c) 1987-2022 by the citadel.org team
5 // This program is open source software. Use, duplication, or disclosure is subject to the GNU General Public License v3.
9 struct ctdlsession *cpool = NULL; // linked list of connections to the Citadel server
10 pthread_mutex_t cpool_mutex = PTHREAD_MUTEX_INITIALIZER; // Lock it before modifying
13 // Read a specific number of bytes of binary data from the Citadel server.
14 // Returns the number of bytes read or -1 for error.
15 int ctdl_read_binary(struct ctdlsession *ctdl, char *buf, int bytes_requested) {
19 while (bytes_read < bytes_requested) {
20 c = read(ctdl->sock, &buf[bytes_read], bytes_requested-bytes_read);
22 syslog(LOG_DEBUG, "Socket error or zero-length read");
31 // Read a newline-terminated line of text from the Citadel server.
32 // Returns the string length or -1 for error.
33 int ctdl_readline(struct ctdlsession *ctdl, char *buf, int maxbytes) {
41 while (len < maxbytes) {
42 c = read(ctdl->sock, &buf[len], 1);
44 syslog(LOG_DEBUG, "Socket error or zero-length read");
47 if (buf[len] == '\n') {
48 if ((len > 0) && (buf[len - 1] == '\r')) {
52 // syslog(LOG_DEBUG, "[ %s", buf);
57 // syslog(LOG_DEBUG, "[ %s", buf);
62 // Read lines of text from the Citadel server until a 000 terminator is received.
63 // Implemented in terms of ctdl_readline() and is therefore transparent...
64 // Returns a newly allocated StrBuf or NULL for error.
65 StrBuf *ctdl_readtextmsg(struct ctdlsession *ctdl) {
67 StrBuf *sj = NewStrBuf();
72 while (ctdl_readline(ctdl, buf, sizeof(buf)), strcmp(buf, "000")) {
73 StrBufAppendPrintf(sj, "%s\n", buf);
80 // Write to the Citadel server. For now we're just wrapping write() in case we
81 // need to add anything else later.
82 ssize_t ctdl_write(struct ctdlsession *ctdl, const void *buf, size_t count) {
83 return write(ctdl->sock, buf, count);
87 // printf() type function to send data to the Citadel Server.
88 void ctdl_printf(struct ctdlsession *ctdl, const char *format, ...) {
90 StrBuf *Buf = NewStrBuf();
92 va_start(arg_ptr, format);
93 StrBufVAppendPrintf(Buf, format, arg_ptr);
96 // syslog(LOG_DEBUG, "] %s", ChrPtr(Buf));
97 ctdl_write(ctdl, (char *) ChrPtr(Buf), StrLength(Buf));
98 ctdl_write(ctdl, "\n", 1);
103 // Client side - connect to a unix domain socket
104 int uds_connectsock(char *sockpath) {
105 struct sockaddr_un addr;
108 memset(&addr, 0, sizeof(addr));
109 addr.sun_family = AF_UNIX;
110 strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
112 s = socket(AF_UNIX, SOCK_STREAM, 0);
114 syslog(LOG_WARNING, "Can't create socket [%s]: %s", sockpath, strerror(errno));
118 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
119 syslog(LOG_WARNING, "Can't connect [%s]: %s", sockpath, strerror(errno));
127 // Extract from the headers, the username and password the client is attempting to use.
128 // This could be HTTP AUTH or it could be in the cookies.
129 void extract_auth(struct http_transaction *h, char *authbuf, int authbuflen) {
130 if (authbuf == NULL) {
134 memset(authbuf, 0, authbuflen);
136 char *authheader = header_val(h, "Authorization");
138 if (!strncasecmp(authheader, "Basic ", 6)) {
139 safestrncpy(authbuf, &authheader[6], authbuflen);
140 return; // HTTP-AUTH was found -- stop here
144 char *cookieheader = header_val(h, "Cookie");
146 char *wcauth = strstr(cookieheader, "wcauth=");
148 safestrncpy(authbuf, &cookieheader[7], authbuflen);
149 char *semicolon = strchr(authbuf, ';');
150 if (semicolon != NULL) {
153 if (strlen(authbuf) < 3) { // impossibly small
156 return; // Cookie auth was found -- stop here
159 // no authorization found in headers ... this is an anonymous session
163 // Log in to the Citadel server. Returns 0 on success or nonzero on error.
165 // 'auth' should be a base64-encoded "username:password" combination (like in http-auth)
167 // If 'resultbuf' is not NULL, it should be a buffer of at least 1024 characters,
168 // and will be filled with the result from a Citadel server command.
169 int login_to_citadel(struct ctdlsession *c, char *auth, char *resultbuf) {
173 char supplied_username[AUTH_MAX];
174 char supplied_password[AUTH_MAX];
176 if (resultbuf != NULL) {
183 buflen = CtdlDecodeBase64(buf, auth, strlen(auth));
184 extract_token(supplied_username, buf, 0, ':', sizeof supplied_username);
185 extract_token(supplied_password, buf, 1, ':', sizeof supplied_password);
186 syslog(LOG_DEBUG, "Supplied credentials: username=%s, password=(%d bytes)", supplied_username, (int) strlen(supplied_password));
188 ctdl_printf(c, "USER %s", supplied_username);
189 ctdl_readline(c, buf, 1024);
191 syslog(LOG_DEBUG, "No such user: %s", buf);
192 return(1); // no such user; resultbuf will explain why
195 ctdl_printf(c, "PASS %s", supplied_password);
196 ctdl_readline(c, buf, 1024);
199 extract_token(c->whoami, &buf[4], 0, '|', sizeof c->whoami);
200 syslog(LOG_DEBUG, "Logged in as %s", c->whoami);
202 // Re-encode the auth string so it contains the properly formatted username
203 char new_auth_string[1024];
204 snprintf(new_auth_string, sizeof(new_auth_string), "%s:%s", c->whoami, supplied_password);
205 CtdlEncodeBase64(c->auth, new_auth_string, strlen(new_auth_string), BASE64_NO_LINEBREAKS);
210 syslog(LOG_DEBUG, "Login failed: %s", &buf[4]);
211 return(1); // login failed; resultbuf will explain why
215 // This is a variant of the "server connection pool" design pattern. We go through our list
216 // of connections to Citadel Server, looking for a connection that is at once:
217 // 1. Not currently serving a WebCit transaction (is_bound)
218 // 2a. Is logged in to Citadel as the correct user, if the HTTP session is logged in; or
219 // 2b. Is NOT logged in to Citadel, if the HTTP session is not logged in.
220 // If we find a qualifying connection, we bind to it for the duration of this WebCit HTTP transaction.
221 // Otherwise, we create a new connection to Citadel Server and add it to the pool.
222 struct ctdlsession *connect_to_citadel(struct http_transaction *h) {
223 struct ctdlsession *cptr = NULL;
224 struct ctdlsession *my_session = NULL;
225 int is_new_session = 0;
230 // Does the request carry a username and password?
231 extract_auth(h, auth, sizeof auth);
233 // Lock the connection pool while we claim our connection
234 pthread_mutex_lock(&cpool_mutex);
236 for (cptr = cpool; ((cptr != NULL) && (my_session == NULL)); cptr = cptr->next) {
237 if ((cptr->is_bound == 0) && (!strcmp(cptr->auth, auth))) {
239 my_session->is_bound = 1;
243 if (my_session == NULL) {
244 syslog(LOG_DEBUG, "No qualifying sessions , starting a new one");
245 my_session = malloc(sizeof(struct ctdlsession));
246 if (my_session != NULL) {
247 memset(my_session, 0, sizeof(struct ctdlsession));
249 my_session->next = cpool;
251 my_session->is_bound = 1;
254 pthread_mutex_unlock(&cpool_mutex);
255 if (my_session == NULL) {
256 return(NULL); // Could not create a new session (yikes!)
259 if (my_session->sock < 3) {
262 else { // make sure our Citadel session is still working
264 test_conn = ctdl_write(my_session, HKEY("NOOP\n"));
266 syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
267 close(my_session->sock);
268 my_session->sock = 0;
272 test_conn = ctdl_readline(my_session, buf, sizeof(buf));
274 syslog(LOG_DEBUG, "Citadel session is broken , must reconnect");
275 close(my_session->sock);
276 my_session->sock = 0;
282 if (is_new_session) {
283 strcpy(my_session->room, "");
284 static char *ctdl_sock_path = NULL;
285 if (!ctdl_sock_path) {
286 ctdl_sock_path = malloc(PATH_MAX);
287 snprintf(ctdl_sock_path, PATH_MAX, "%s/citadel.socket", ctdl_dir);
289 my_session->sock = uds_connectsock(ctdl_sock_path);
290 ctdl_readline(my_session, buf, sizeof(buf)); // skip past the server greeting banner
292 if (!IsEmptyStr(auth)) { // do we need to log in to Citadel?
293 r = login_to_citadel(my_session, auth, NULL); // FIXME figure out what happens if login failed
297 ctdl_printf(my_session, "NOOP");
298 ctdl_readline(my_session, buf, sizeof(buf));
299 my_session->last_access = time(NULL);
300 ++my_session->num_requests_handled;
305 // Release our Citadel Server connection back into the pool.
306 void disconnect_from_citadel(struct ctdlsession *ctdl) {
307 pthread_mutex_lock(&cpool_mutex);
309 pthread_mutex_unlock(&cpool_mutex);