da0f7b974f383ca5e9be9283e71ca64eb8843ea2
[citadel.git] / textclient / citadel_ipc.c
1 /*
2  * Copyright (c) 1987-2018 by the citadel.org team
3  *
4  *  This program is open source software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License version 3.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  */
12
13 #include "textclient.h"
14
15 #ifdef HAVE_OPENSSL
16 static SSL_CTX *ssl_ctx;
17 char arg_encrypt;
18 char rc_encrypt;
19
20 #endif /* HAVE_OPENSSL */
21
22 #ifndef INADDR_NONE
23 #define INADDR_NONE 0xffffffff
24 #endif
25
26 static void (*status_hook)(char *s) = NULL;
27 char ctdl_autoetc_dir[PATH_MAX]="";
28 char file_citadel_rc[PATH_MAX]="";
29 char ctdl_run_dir[PATH_MAX]="";
30 char ctdl_etc_dir[PATH_MAX]="";
31 char ctdl_home_directory[PATH_MAX] = "";
32 char file_citadel_socket[PATH_MAX]="";
33
34 char *viewdefs[]={
35         "Messages",
36         "Summary",
37         "Address book",
38         "Calendar",
39         "Tasks"
40 };
41
42 char *axdefs[]={
43         "Deleted",
44         "New User",
45         "Problem User",
46         "Local User",
47         "Network User",
48         "Preferred User",
49         "Admin",
50         "Admin"
51         };
52
53
54 void CtdlIPC_lock(CtdlIPC *ipc)
55 {
56         if (ipc->network_status_cb) ipc->network_status_cb(1);
57 }
58
59
60 void CtdlIPC_unlock(CtdlIPC *ipc)
61 {
62         if (ipc->network_status_cb) ipc->network_status_cb(0);
63 }
64
65 #ifdef __cplusplus
66 }
67 #endif
68
69
70 char *libcitadelclient_version_string(void) {
71         return "libcitadelclient(unnumbered)";
72 }
73
74
75
76
77 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
78         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
79                          (home&!relh)?ctdl_home_directory:basedir, \
80              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
81              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
82                          relhome, \
83              (relhome[0]!='\0')?"/":"",\
84                          dirbuffer,\
85                          (dirbuffer[0]!='\0')?"/":"");
86
87 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
88
89
90 void calc_dirs_n_files(int relh, int home, const char *relhome, char  *ctdldir, int dbg)
91 {
92         const char* basedir = "";
93         char dirbuffer[PATH_MAX] = "";
94
95         StripSlashes(ctdldir, 1);
96
97 #ifndef HAVE_RUN_DIR
98         basedir=ctdldir;
99 #else
100         basedir=RUN_DIR;
101 #endif
102         COMPUTE_DIRECTORY(ctdl_run_dir);
103         StripSlashes(ctdl_run_dir, 1);
104
105
106 #ifndef HAVE_AUTO_ETC_DIR
107         basedir=ctdldir;
108 #else
109         basedir=AUTO_ETC_DIR;
110 #endif
111         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
112         StripSlashes(ctdl_autoetc_dir, 1);
113
114
115 #ifndef HAVE_ETC_DIR
116         basedir=ctdldir;
117 #else
118         basedir=ETC_DIR;
119 #endif
120         COMPUTE_DIRECTORY(ctdl_etc_dir);
121         StripSlashes(ctdl_etc_dir, 1);
122
123
124
125         snprintf(file_citadel_rc, 
126                          sizeof file_citadel_rc,
127                          "%scitadel.rc",
128                          ctdl_etc_dir);
129         StripSlashes(file_citadel_rc, 0);
130
131         snprintf(file_citadel_socket, 
132                          sizeof file_citadel_socket,
133                                 "%scitadel.socket",
134                          ctdl_run_dir);
135         StripSlashes(file_citadel_socket, 0);
136
137         DBG_PRINT(ctdl_run_dir);
138         DBG_PRINT(file_citadel_socket);
139         DBG_PRINT(ctdl_etc_dir);
140         DBG_PRINT(file_citadel_rc);
141 }
142
143 void setCryptoStatusHook(void (*hook)(char *s)) {
144         status_hook = hook;
145 }
146
147 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
148         ipc->network_status_cb = hook;
149 }
150
151
152 char instant_msgs = 0;
153
154
155 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
156 static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
157 #ifdef HAVE_OPENSSL
158 static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
159 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
160 static void endtls(SSL *ssl);
161 #endif /* HAVE_OPENSSL */
162 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
163 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
164
165
166
167 const char *svn_revision(void);
168
169 /*
170  * Does nothing.  The server should always return 200.
171  */
172 int CtdlIPCNoop(CtdlIPC *ipc)
173 {
174         char aaa[128];
175
176         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
177 }
178
179
180 /*
181  * Does nothing interesting.  The server should always return 200
182  * along with your string.
183  */
184 int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
185 {
186         int ret;
187         char *aaa;
188         
189         if (!arg) return -2;
190         if (!cret) return -2;
191
192         aaa = (char *)malloc((size_t)(strlen(arg) + 6));
193         if (!aaa) return -1;
194
195         sprintf(aaa, "ECHO %s", arg);
196         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
197         free(aaa);
198         return ret;
199 }
200
201
202 /*
203  * Asks the server to close the connecction.
204  * Should always return 200.
205  */
206 int CtdlIPCQuit(CtdlIPC *ipc)
207 {
208         int ret = 221;          /* Default to successful quit */
209         char aaa[SIZ]; 
210
211         CtdlIPC_lock(ipc);
212         if (ipc->sock > -1) {
213                 CtdlIPC_putline(ipc, "QUIT");
214                 CtdlIPC_getline(ipc, aaa);
215                 ret = atoi(aaa);
216         }
217 #ifdef HAVE_OPENSSL
218         if (ipc->ssl)
219                 SSL_shutdown(ipc->ssl);
220         ipc->ssl = NULL;
221 #endif
222         if (ipc->sock)
223                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
224         ipc->sock = -1;
225         CtdlIPC_unlock(ipc);
226         return ret;
227 }
228
229
230 /*
231  * Asks the server to log out.  Should always return 200, even if no user
232  * was logged in.  The user will not be logged in after this!
233  */
234 int CtdlIPCLogout(CtdlIPC *ipc)
235 {
236         int ret;
237         char aaa[SIZ];
238
239         CtdlIPC_lock(ipc);
240         CtdlIPC_putline(ipc, "LOUT");
241         CtdlIPC_getline(ipc, aaa);
242         ret = atoi(aaa);
243         CtdlIPC_unlock(ipc);
244         return ret;
245 }
246
247
248 /*
249  * First stage of authentication - pass the username.  Returns 300 if the
250  * username is able to log in, with the username correctly spelled in cret.
251  * Returns various 500 error codes if the user doesn't exist, etc.
252  */
253 int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
254 {
255         int ret;
256         char *aaa;
257
258         if (!username) return -2;
259         if (!cret) return -2;
260
261         aaa = (char *)malloc((size_t)(strlen(username) + 6));
262         if (!aaa) return -1;
263
264         sprintf(aaa, "USER %s", username);
265         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
266         free(aaa);
267         return ret;
268 }
269
270
271 /*
272  * Second stage of authentication - provide password.  The server returns
273  * 200 and several arguments in cret relating to the user's account.
274  */
275 int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
276 {
277         int ret;
278         char *aaa;
279
280         if (!passwd) return -2;
281         if (!cret) return -2;
282
283         aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
284         if (!aaa) return -1;
285
286         sprintf(aaa, "PASS %s", passwd);
287         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
288         free(aaa);
289         return ret;
290 }
291
292
293 /*
294  * Second stage of authentication - provide password.  The server returns
295  * 200 and several arguments in cret relating to the user's account.
296  */
297 int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
298 {
299         int ret;
300         char *aaa;
301
302         if (!response) return -2;
303         if (!cret) return -2;
304
305         aaa = (char *)malloc((size_t)(strlen(response) + 6));
306         if (!aaa) return -1;
307
308         sprintf(aaa, "PAS2 %s", response);
309         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
310         free(aaa);
311         return ret;
312 }
313
314
315 /*
316  * Create a new user.  This returns 200 plus the same arguments as TryPassword
317  * if selfservice is nonzero, unless there was a problem creating the account.
318  * If selfservice is zero, creates a new user but does not log out the existing
319  * user - intended for use by system administrators to create accounts on
320  * behalf of other users.
321  */
322 int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
323 {
324         int ret;
325         char *aaa;
326
327         if (!username) return -2;
328         if (!cret) return -2;
329
330         aaa = (char *)malloc((size_t)(strlen(username) + 6));
331         if (!aaa) return -1;
332
333         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU",  username);
334         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
335         free(aaa);
336         return ret;
337 }
338
339
340 /*
341  * Changes the user's password.  Returns 200 if changed, errors otherwise.
342  */
343 int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
344 {
345         int ret;
346         char *aaa;
347
348         if (!passwd) return -2;
349         if (!cret) return -2;
350
351         aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
352         if (!aaa) return -1;
353
354         sprintf(aaa, "SETP %s", passwd);
355         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
356         free(aaa);
357         return ret;
358 }
359
360
361 /* LKRN */
362 /* Caller must free the march list */
363 /* Room types are defined in enum RoomList; keep these in sync! */
364 /* floor is -1 for all, or floornum */
365 int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
366 {
367         int ret;
368         struct march *march = NULL;
369         static char *proto[] =
370                 {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
371         char aaa[SIZ];
372         char *bbb = NULL;
373         size_t bbb_len;
374
375         if (!listing) return -2;
376         if (*listing) return -2;        /* Free the listing first */
377         if (!cret) return -2;
378         /* if (which < 0 || which > 4) return -2; */
379         if (floor < -1) return -2;      /* Can't validate upper bound, sorry */
380
381         sprintf(aaa, "%s %d", proto[which], floor);
382         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
383         if (ret / 100 == 1) {
384                 struct march *mptr;
385
386                 while (bbb && strlen(bbb)) {
387                         int a;
388
389                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
390                         a = strlen(aaa);
391                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
392                         mptr = (struct march *) malloc(sizeof (struct march));
393                         if (mptr) {
394                                 mptr->next = NULL;
395                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
396                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
397                                 mptr->march_floor = (char) extract_int(aaa, 2);
398                                 mptr->march_order = (char) extract_int(aaa, 3);
399                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
400                                 mptr->march_access = (char) extract_int(aaa, 5);
401                                 if (march == NULL)
402                                         march = mptr;
403                                 else {
404                                         struct march *mptr2;
405
406                                         mptr2 = march;
407                                         while (mptr2->next != NULL)
408                                                 mptr2 = mptr2->next;
409                                         mptr2->next = mptr;
410                                 }
411                         }
412                 }
413         }
414         *listing = march;
415         if (bbb) free(bbb);
416         return ret;
417 }
418
419
420 /* GETU */
421 /* Caller must free the struct ctdluser; caller may pass an existing one */
422 int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
423 {
424         int ret;
425
426         if (!cret) return -2;
427         if (!uret) return -2;
428         if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
429         if (!*uret) return -1;
430
431         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
432         if (ret / 100 == 2) {
433                 uret[0]->flags = extract_int(cret, 2);
434         }
435         return ret;
436 }
437
438
439 /* SETU */
440 int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
441 {
442         char aaa[48];
443
444         if (!uret) return -2;
445         if (!cret) return -2;
446
447         sprintf(aaa,
448                 "SETU 80|24|%d",
449                 uret->flags
450         );
451         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
452 }
453
454
455 /* RENU */
456 int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
457 {
458         int ret;
459         char cmd[256];
460
461         if (!oldname) return -2;
462         if (!newname) return -2;
463         if (!cret) return -2;
464
465         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
466         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
467         return ret;
468 }
469
470
471 /* GOTO */
472 int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
473                 struct ctdlipcroom **rret, char *cret)
474 {
475         int ret;
476         char *aaa;
477
478         if (!cret) return -2;
479         if (!rret) return -2;
480         if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
481         if (!*rret) return -1;
482
483         if (passwd) {
484                 aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
485                 if (!aaa) {
486                         free(*rret);
487                         return -1;
488                 }
489                 sprintf(aaa, "GOTO %s|%s", room, passwd);
490         } else {
491                 aaa = (char *)malloc(strlen(room) + 6);
492                 if (!aaa) {
493                         free(*rret);
494                         return -1;
495                 }
496                 sprintf(aaa, "GOTO %s", room);
497         }
498         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
499         if (ret / 100 == 2) {
500                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
501                 rret[0]->RRunread = extract_long(cret, 1);
502                 rret[0]->RRtotal = extract_long(cret, 2);
503                 rret[0]->RRinfoupdated = extract_int(cret, 3);
504                 rret[0]->RRflags = extract_int(cret, 4);
505                 rret[0]->RRhighest = extract_long(cret, 5);
506                 rret[0]->RRlastread = extract_long(cret, 6);
507                 rret[0]->RRismailbox = extract_int(cret, 7);
508                 rret[0]->RRaide = extract_int(cret, 8);
509                 rret[0]->RRnewmail = extract_long(cret, 9);
510                 rret[0]->RRfloor = extract_int(cret, 10);
511                 rret[0]->RRcurrentview = extract_int(cret, 11);
512                 rret[0]->RRdefaultview = extract_int(cret, 12);
513                 /* position 13 is a trash folder flag ... irrelevant in this client */
514                 rret[0]->RRflags2 = extract_int(cret, 14);
515         } else {
516                 free(*rret);
517                 *rret = NULL;
518         }
519         free(aaa);
520         return ret;
521 }
522
523
524 /* MSGS */
525 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
526 /* whicharg is number of messages, applies to last, first, gt, lt */
527 int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
528                 const char *mtemplate, unsigned long **mret, char *cret)
529 {
530         int ret;
531         unsigned long count = 0;
532         static char *proto[] =
533                 { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
534         char aaa[33];
535         char *bbb = NULL;
536         size_t bbb_len;
537
538         if (!cret) return -2;
539         if (!mret) return -2;
540         if (*mret) return -2;
541         if (which < 0 || which > 6) return -2;
542
543         if (which <= 2)
544                 sprintf(aaa, "MSGS %s||%d", proto[which],
545                                 (mtemplate) ? 1 : 0);
546         else
547                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
548                                 (mtemplate) ? 1 : 0);
549         if (mtemplate) count = strlen(mtemplate);
550         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
551         if (ret / 100 != 1)
552                 return ret;
553         count = 0;
554         *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
555         if (!*mret)
556                 return -1;
557         while (bbb && strlen(bbb)) {
558                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
559                 remove_token(bbb, 0, '\n');
560                 *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
561                                         sizeof (unsigned long)));
562                 if (*mret) {
563                         (*mret)[count++] = atol(aaa);
564                         (*mret)[count] = 0L;
565                 } else {
566                         break;
567                 }
568         }
569         if (bbb) free(bbb);
570         return ret;
571 }
572
573
574 /* MSG0, MSG2 */
575 int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
576                 struct ctdlipcmessage **mret, char *cret)
577 {
578         int ret;
579         char aaa[SIZ];
580         char *bbb = NULL;
581         size_t bbb_len;
582         int multipart_hunting = 0;
583         char multipart_prefix[128];
584         char encoding[256];
585
586         if (!cret) return -1;
587         if (!mret) return -1;
588         if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
589         if (!*mret) return -1;
590         if (!msgnum) return -1;
591
592         strcpy(encoding, "");
593         strcpy(mret[0]->content_type, "");
594         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
595         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
596         if (ret / 100 == 1) {
597                 if (as_mime != 2) {
598                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
599                         while (strlen(bbb) > 4 && bbb[4] == '=') {
600                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
601                                 remove_token(bbb, 0, '\n');
602
603                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
604                                         mret[0]->nhdr = 1;
605                                 else if (!strncasecmp(aaa, "from=", 5))
606                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
607                                 else if (!strncasecmp(aaa, "type=", 5))
608                                         mret[0]->type = atoi(&aaa[5]);
609                                 else if (!strncasecmp(aaa, "msgn=", 5))
610                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
611                                 else if (!strncasecmp(aaa, "subj=", 5))
612                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
613                                 else if (!strncasecmp(aaa, "rfca=", 5))
614                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
615                                 else if (!strncasecmp(aaa, "room=", 5))
616                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
617                                 else if (!strncasecmp(aaa, "rcpt=", 5))
618                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
619                                 else if (!strncasecmp(aaa, "wefw=", 5))
620                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
621                                 else if (!strncasecmp(aaa, "time=", 5))
622                                         mret[0]->time = atol(&aaa[5]);
623
624                                 /* Multipart/alternative prefix & suffix strings help
625                                  * us to determine which part we want to download.
626                                  */
627                                 else if (!strncasecmp(aaa, "pref=", 5)) {
628                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
629                                         if (!strcasecmp(multipart_prefix,
630                                            "multipart/alternative")) {
631                                                 ++multipart_hunting;
632                                         }
633                                 }
634                                 else if (!strncasecmp(aaa, "suff=", 5)) {
635                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
636                                         if (!strcasecmp(multipart_prefix,
637                                            "multipart/alternative")) {
638                                                 ++multipart_hunting;
639                                         }
640                                 }
641
642                                 else if (!strncasecmp(aaa, "part=", 5)) {
643                                         struct parts *ptr, *chain;
644         
645                                         ptr = (struct parts *)calloc(1, sizeof (struct parts));
646                                         if (ptr) {
647
648                                                 /* Fill the buffers for the caller */
649                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
650                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
651                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
652                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
653                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
654                                                 ptr->length = extract_long(&aaa[5], 5);
655                                                 if (!mret[0]->attachments)
656                                                         mret[0]->attachments = ptr;
657                                                 else {
658                                                         chain = mret[0]->attachments;
659                                                         while (chain->next)
660                                                                 chain = chain->next;
661                                                         chain->next = ptr;
662                                                 }
663
664                                                 /* Now handle multipart/alternative */
665                                                 if (multipart_hunting > 0) {
666                                                         if ( (!strcasecmp(ptr->mimetype,
667                                                              "text/plain"))
668                                                            || (!strcasecmp(ptr->mimetype,
669                                                               "text/html")) ) {
670                                                                 strcpy(mret[0]->mime_chosen,
671                                                                         ptr->number);
672                                                         }
673                                                 }
674
675                                         }
676                                 }
677                         }
678                         /* Eliminate "text\n" */
679                         remove_token(bbb, 0, '\n');
680
681                         /* If doing a MIME thing, pull out the extra headers */
682                         if (as_mime == 4) {
683                                 do {
684                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
685                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
686                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
687                                                 striplt(mret[0]->content_type);
688
689                                                 /* strip out ";charset=" portion.  FIXME do something with
690                                                  * the charset (like... convert it) instead of just throwing
691                                                  * it away
692                                                  */
693                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
694                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
695                                                 }
696
697                                         }
698                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
699                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
700                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
701                                                 striplt(mret[0]->mime_chosen);
702                                         }
703                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
704                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
705                                                 strcpy(encoding, &encoding[26]);
706                                                 striplt(encoding);
707                                         }
708                                         remove_token(bbb, 0, '\n');
709                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
710                                 remove_token(bbb, 0, '\n');
711                         }
712
713
714                 }
715                 if (strlen(bbb)) {
716
717                         if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
718                                 char *ccc = NULL;
719                                 int bytes_decoded = 0;
720                                 ccc = malloc(strlen(bbb) + 32768);
721                                 if (!strcasecmp(encoding, "base64")) {
722                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
723                                 }
724                                 else if (!strcasecmp(encoding, "quoted-printable")) {
725                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
726                                 }
727                                 ccc[bytes_decoded] = 0;
728                                 free(bbb);
729                                 bbb = ccc;
730                         }
731
732                         /* FIXME: Strip trailing whitespace */
733                         bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
734
735                 } else {
736                         bbb = (char *)realloc(bbb, 1);
737                         *bbb = '\0';
738                 }
739                 mret[0]->text = bbb;
740         }
741         return ret;
742 }
743
744
745 /* WHOK */
746 int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
747 {
748         int ret;
749         size_t bytes;
750
751         if (!cret) return -2;
752         if (!listing) return -2;
753         if (*listing) return -2;
754
755         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
756         return ret;
757 }
758
759
760 /* INFO */
761 int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
762 {
763         int ret;
764         size_t bytes;
765         char *listing = NULL;
766         char buf[SIZ];
767
768         if (!cret) return -2;
769
770         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
771         if (ret / 100 == 1) {
772                 int line = 0;
773
774                 while (*listing && strlen(listing)) {
775                         extract_token(buf, listing, 0, '\n', sizeof buf);
776                         remove_token(listing, 0, '\n');
777                         switch (line++) {
778                         case 0:         ipc->ServInfo.pid = atoi(buf);
779                                         break;
780                         case 1:         strcpy(ipc->ServInfo.nodename,buf);
781                                         break;
782                         case 2:         strcpy(ipc->ServInfo.humannode,buf);
783                                         break;
784                         case 3:         strcpy(ipc->ServInfo.fqdn,buf);
785                                         break;
786                         case 4:         strcpy(ipc->ServInfo.software,buf);
787                                         break;
788                         case 5:         ipc->ServInfo.rev_level = atoi(buf);
789                                         break;
790                         case 6:         strcpy(ipc->ServInfo.site_location,buf);
791                                         break;
792                         case 7:         strcpy(ipc->ServInfo.sysadm,buf);
793                                         break;
794                         case 9:         strcpy(ipc->ServInfo.moreprompt,buf);
795                                         break;
796                         case 10:        ipc->ServInfo.ok_floors = atoi(buf);
797                                         break;
798                         case 11:        ipc->ServInfo.paging_level = atoi(buf);
799                                         break;
800                         case 13:        ipc->ServInfo.supports_qnop = atoi(buf);
801                                         break;
802                         case 14:        ipc->ServInfo.supports_ldap = atoi(buf);
803                                         break;
804                         case 15:        ipc->ServInfo.newuser_disabled = atoi(buf);
805                                         break;
806                         case 16:        strcpy(ipc->ServInfo.default_cal_zone, buf);
807                                         break;
808                         case 17:        ipc->ServInfo.load_avg = atof(buf);
809                                         break;
810                         case 18:        ipc->ServInfo.worker_avg = atof(buf);
811                                         break;
812                         case 19:        ipc->ServInfo.thread_count = atoi(buf);
813                                         break;
814                         case 20:        ipc->ServInfo.has_sieve = atoi(buf);
815                                         break;
816                         case 21:        ipc->ServInfo.fulltext_enabled = atoi(buf);
817                                         break;
818                         case 22:        strcpy(ipc->ServInfo.svn_revision, buf);
819                                         break;
820                         case 24:        ipc->ServInfo.guest_logins = atoi(buf);
821                                         break;
822                         }
823                 }
824
825         }
826         if (listing) free(listing);
827         return ret;
828 }
829
830
831 /* RDIR */
832 int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
833 {
834         int ret;
835         size_t bytes;
836
837         if (!cret) return -2;
838         if (!listing) return -2;
839         if (*listing) return -2;
840
841         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
842         return ret;
843 }
844
845
846 /*
847  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
848  */
849 int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
850 {
851         int ret;
852         char aaa[64];
853
854         if (!cret) return -2;
855
856         if (msgnum) {
857                 sprintf(aaa, "SLRP %ld", msgnum);
858         }
859         else {
860                 sprintf(aaa, "SLRP HIGHEST");
861         }
862         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
863         return ret;
864 }
865
866
867 /* INVT */
868 int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
869 {
870         int ret;
871         char *aaa;
872
873         if (!cret) return -2;
874         if (!username) return -2;
875
876         aaa = (char *)malloc(strlen(username) + 6);
877         if (!aaa) return -1;
878
879         sprintf(aaa, "INVT %s", username);
880         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
881         free(aaa);
882         return ret;
883 }
884
885
886 /* KICK */
887 int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
888 {
889         int ret;
890         char *aaa;
891
892         if (!cret) return -1;
893         if (!username) return -1;
894
895         aaa = (char *)malloc(strlen(username) + 6);
896
897         sprintf(aaa, "KICK %s", username);
898         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
899         free(aaa);
900         return ret;
901 }
902
903
904 /* GETR */
905 int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
906 {
907         int ret;
908
909         if (!cret) return -2;
910         if (!qret) return -2;
911         if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
912         if (!*qret) return -1;
913
914         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
915         if (ret / 100 == 2) {
916                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
917                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
918                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
919                 qret[0]->QRflags = extract_int(cret, 3);
920                 qret[0]->QRfloor = extract_int(cret, 4);
921                 qret[0]->QRorder = extract_int(cret, 5);
922                 qret[0]->QRdefaultview = extract_int(cret, 6);
923                 qret[0]->QRflags2 = extract_int(cret, 7);
924         }
925         return ret;
926 }
927
928
929 /* SETR */
930 /* set forget to kick all users out of room */
931 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
932 {
933         int ret;
934         char *aaa;
935
936         if (!cret) return -2;
937         if (!qret) return -2;
938
939         aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
940                         strlen(qret->QRdirname) + 64);
941         if (!aaa) return -1;
942
943         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
944                         qret->QRname, qret->QRpasswd, qret->QRdirname,
945                         qret->QRflags, forget, qret->QRfloor, qret->QRorder,
946                         qret->QRdefaultview, qret->QRflags2);
947         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
948         free(aaa);
949         return ret;
950 }
951
952
953 /* GETA */
954 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
955 {
956         if (!cret) return -1;
957
958         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
959 }
960
961
962 /* SETA */
963 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
964 {
965         int ret;
966         char *aaa;
967
968         if (!cret) return -2;
969         if (!username) return -2;
970
971         aaa = (char *)malloc(strlen(username) + 6);
972         if (!aaa) return -1;
973
974         sprintf(aaa, "SETA %s", username);
975         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
976         free(aaa);
977         return ret;
978 }
979
980
981 /* ENT0 */
982 int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required,  struct ctdlipcmessage *mr, char *cret)
983 {
984         int ret;
985         char cmd[SIZ];
986         char *ptr;
987
988         if (!cret) return -2;
989         if (!mr) return -2;
990
991         if (mr->references) {
992                 for (ptr=mr->references; *ptr != 0; ++ptr) {
993                         if (*ptr == '|') *ptr = '!';
994                 }
995         }
996
997         snprintf(cmd, sizeof cmd,
998                         "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
999                         mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1000         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
1001                         NULL, cret);
1002         if ((flag == 0) && (subject_required != NULL)) {
1003                 /* Is the server strongly recommending that the user enter a message subject? */
1004                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1005                         *subject_required = extract_int(&cret[4], 1);
1006                 }
1007
1008                 
1009         }
1010         return ret;
1011 }
1012
1013
1014 /* RINF */
1015 int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
1016 {
1017         size_t bytes;
1018
1019         if (!cret) return -2;
1020         if (!iret) return -2;
1021         if (*iret) return -2;
1022
1023         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1024 }
1025
1026
1027 /* DELE */
1028 int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
1029 {
1030         char aaa[64];
1031
1032         if (!cret) return -2;
1033         if (!msgnum) return -2;
1034
1035         sprintf(aaa, "DELE %ld", msgnum);
1036         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1037 }
1038
1039
1040 /* MOVE */
1041 int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
1042 {
1043         int ret;
1044         char *aaa;
1045
1046         if (!cret) return -2;
1047         if (!destroom) return -2;
1048         if (!msgnum) return -2;
1049
1050         aaa = (char *)malloc(strlen(destroom) + 28);
1051         if (!aaa) return -1;
1052
1053         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1054         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1055         free(aaa);
1056         return ret;
1057 }
1058
1059
1060 /* KILL */
1061 int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
1062 {
1063         char aaa[64];
1064
1065         if (!cret) return -2;
1066
1067         sprintf(aaa, "KILL %d", for_real);
1068         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1069 }
1070
1071
1072 /* CRE8 */
1073 int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
1074                 const char *password, int floor, char *cret)
1075 {
1076         int ret;
1077         char *aaa;
1078
1079         if (!cret) return -2;
1080         if (!roomname) return -2;
1081
1082         if (password) {
1083                 aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
1084                 if (!aaa) return -1;
1085                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
1086                                 password, floor);
1087         } else {
1088                 aaa = (char *)malloc(strlen(roomname) + 40);
1089                 if (!aaa) return -1;
1090                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
1091                                 floor);
1092         }
1093         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1094         free(aaa);
1095         return ret;
1096 }
1097
1098
1099 /* FORG */
1100 int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
1101 {
1102         if (!cret) return -2;
1103
1104         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1105 }
1106
1107
1108 /* MESG */
1109 int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
1110 {
1111         int ret;
1112         char *aaa;
1113         size_t bytes;
1114
1115         if (!cret) return -2;
1116         if (!mret) return -2;
1117         if (*mret) return -2;
1118         if (!message) return -2;
1119
1120         aaa = (char *)malloc(strlen(message) + 6);
1121         if (!aaa) return -1;
1122
1123         sprintf(aaa, "MESG %s", message);
1124         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1125         free(aaa);
1126         return ret;
1127 }
1128
1129
1130 /* GNUR */
1131 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
1132 {
1133         if (!cret) return -2;
1134
1135         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1136 }
1137
1138
1139 /* GREG */
1140 int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
1141 {
1142         int ret;
1143         char *aaa;
1144         size_t bytes;
1145
1146         if (!cret) return -2;
1147         if (!rret) return -2;
1148         if (*rret) return -2;
1149
1150         if (username)
1151                 aaa = (char *)malloc(strlen(username) + 6);
1152         else
1153                 aaa = (char *)malloc(12);
1154         if (!aaa) return -1;
1155
1156         if (username)
1157                 sprintf(aaa, "GREG %s", username);
1158         else
1159                 sprintf(aaa, "GREG _SELF_");
1160         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1161         free(aaa);
1162         return ret;
1163 }
1164
1165
1166 /* VALI */
1167 int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
1168 {
1169         int ret;
1170         char *aaa;
1171
1172         if (!cret) return -2;
1173         if (!username) return -2;
1174         if (axlevel < AxDeleted || axlevel > AxAideU) return -2;
1175
1176         aaa = (char *)malloc(strlen(username) + 17);
1177         if (!aaa) return -1;
1178
1179         sprintf(aaa, "VALI %s|%d", username, axlevel);
1180         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1181         free(aaa);
1182         return ret;
1183 }
1184
1185
1186 /* EINF */
1187 int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
1188 {
1189         char aaa[64];
1190
1191         if (!cret) return -1;
1192         if (!info) return -1;
1193
1194         sprintf(aaa, "EINF %d", for_real);
1195         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1196 }
1197
1198
1199 /* LIST */
1200 int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
1201 {
1202         size_t bytes;
1203         char *cmd;
1204         int ret;
1205
1206         if (!cret) return -1;
1207         if (!listing) return -1;
1208         if (*listing) return -1;
1209         if (!searchstring) return -1;
1210
1211         cmd = malloc(strlen(searchstring) + 10);
1212         sprintf(cmd, "LIST %s", searchstring);
1213
1214         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1215         free(cmd);
1216         return(ret);
1217 }
1218
1219
1220 /* REGI */
1221 int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
1222 {
1223         if (!cret) return -1;
1224         if (!info) return -1;
1225
1226         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
1227                         NULL, NULL, cret);
1228 }
1229
1230
1231 /* CHEK */
1232 int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
1233 {
1234         int ret;
1235
1236         if (!cret) return -1;
1237         if (!chek) return -1;
1238
1239         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1240         if (ret / 100 == 2) {
1241                 chek->newmail = extract_long(cret, 0);
1242                 chek->needregis = extract_int(cret, 1);
1243                 chek->needvalid = extract_int(cret, 2);
1244         }
1245         return ret;
1246 }
1247
1248
1249 /* DELF */
1250 int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
1251 {
1252         int ret;
1253         char *aaa;
1254
1255         if (!cret) return -2;
1256         if (!filename) return -2;
1257         
1258         aaa = (char *)malloc(strlen(filename) + 6);
1259         if (!aaa) return -1;
1260
1261         sprintf(aaa, "DELF %s", filename);
1262         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1263         free(aaa);
1264         return ret;
1265 }
1266
1267
1268 /* MOVF */
1269 int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
1270 {
1271         int ret;
1272         char *aaa;
1273
1274         if (!cret) return -2;
1275         if (!filename) return -2;
1276         if (!destroom) return -2;
1277
1278         aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
1279         if (!aaa) return -1;
1280
1281         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1282         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1283         free(aaa);
1284         return ret;
1285 }
1286
1287
1288 /* RWHO */
1289 int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
1290 {
1291         int ret;
1292         size_t bytes;
1293
1294         if (!cret) return -1;
1295         if (!listing) return -1;
1296         if (*listing) return -1;
1297
1298         *stamp = CtdlIPCServerTime(ipc, cret);
1299         if (!*stamp)
1300                 *stamp = time(NULL);
1301         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1302         return ret;
1303 }
1304
1305
1306 /* OPEN */
1307 int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
1308                 size_t resume,
1309                 void (*progress_gauge_callback)
1310                         (CtdlIPC*, unsigned long, unsigned long),
1311                 char *cret)
1312 {
1313         int ret;
1314         size_t bytes;
1315         time_t last_mod;
1316         char mimetype[SIZ];
1317         char *aaa;
1318
1319         if (!cret) return -2;
1320         if (!filename) return -2;
1321         if (!buf) return -2;
1322         if (*buf) return -2;
1323         if (ipc->downloading) return -2;
1324
1325         aaa = (char *)malloc(strlen(filename) + 6);
1326         if (!aaa) return -1;
1327
1328         sprintf(aaa, "OPEN %s", filename);
1329         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1330         free(aaa);
1331         if (ret / 100 == 2) {
1332                 ipc->downloading = 1;
1333                 bytes = extract_long(cret, 0);
1334                 last_mod = extract_int(cret, 1);
1335                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1336
1337                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
1338                                         progress_gauge_callback, cret);
1339                 /*
1340                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1341                                         progress_gauge_callback, cret);
1342                 */
1343
1344                 ret = CtdlIPCEndDownload(ipc, cret);
1345                 if (ret / 100 == 2)
1346                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1347                                         filename, mimetype);
1348         }
1349         return ret;
1350 }
1351
1352
1353 /* OPNA */
1354 int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
1355                 void **buf,
1356                 void (*progress_gauge_callback)
1357                         (CtdlIPC*, unsigned long, unsigned long),
1358                 char *cret)
1359 {
1360         int ret;
1361         size_t bytes;
1362         time_t last_mod;
1363         char filename[SIZ];
1364         char mimetype[SIZ];
1365         char aaa[SIZ];
1366
1367         if (!cret) return -2;
1368         if (!buf) return -2;
1369         if (*buf) return -2;
1370         if (!part) return -2;
1371         if (!msgnum) return -2;
1372         if (ipc->downloading) return -2;
1373
1374         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1375         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1376         if (ret / 100 == 2) {
1377                 ipc->downloading = 1;
1378                 bytes = extract_long(cret, 0);
1379                 last_mod = extract_int(cret, 1);
1380                 extract_token(filename, cret, 2, '|', sizeof filename);
1381                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1382                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1383                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1384                 ret = CtdlIPCEndDownload(ipc, cret);
1385                 if (ret / 100 == 2)
1386                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1387                                         filename, mimetype);
1388         }
1389         return ret;
1390 }
1391
1392
1393 /* OIMG */
1394 int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
1395                 void (*progress_gauge_callback)
1396                         (CtdlIPC*, unsigned long, unsigned long),
1397                 char *cret)
1398 {
1399         int ret;
1400         size_t bytes;
1401         time_t last_mod;
1402         char mimetype[SIZ];
1403         char *aaa;
1404
1405         if (!cret) return -1;
1406         if (!buf) return -1;
1407         if (*buf) return -1;
1408         if (!filename) return -1;
1409         if (ipc->downloading) return -1;
1410
1411         aaa = (char *)malloc(strlen(filename) + 6);
1412         if (!aaa) return -1;
1413
1414         sprintf(aaa, "OIMG %s", filename);
1415         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1416         free(aaa);
1417         if (ret / 100 == 2) {
1418                 ipc->downloading = 1;
1419                 bytes = extract_long(cret, 0);
1420                 last_mod = extract_int(cret, 1);
1421                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1422 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1423                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1424                 ret = CtdlIPCEndDownload(ipc, cret);
1425                 if (ret / 100 == 2)
1426                         sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1427                                         filename, mimetype);
1428         }
1429         return ret;
1430 }
1431
1432
1433 /* UOPN */
1434 int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment, 
1435                 const char *path, 
1436                 void (*progress_gauge_callback)
1437                         (CtdlIPC*, unsigned long, unsigned long),
1438                 char *cret)
1439 {
1440         int ret;
1441         char *aaa;
1442         FILE *uploadFP;
1443         char MimeTestBuf[64];
1444         const char *MimeType;
1445         long len;
1446
1447         if (!cret) return -1;
1448         if (!save_as) return -1;
1449         if (!comment) return -1;
1450         if (!path) return -1;
1451         if (!*path) return -1;
1452         if (ipc->uploading) return -1;
1453
1454         uploadFP = fopen(path, "r");
1455         if (!uploadFP) return -2;
1456
1457         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1458         rewind (uploadFP);
1459         if (len < 0) 
1460                 return -3;
1461
1462         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1463         aaa = (char *)malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1464         if (!aaa) return -1;
1465
1466         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType,  comment);
1467         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1468         free(aaa);
1469         if (ret / 100 == 2) {
1470                 ipc->uploading = 1;
1471                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1472                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1473                 ipc->uploading = 0;
1474         }
1475         return ret;
1476 }
1477
1478
1479 /* UIMG */
1480 int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
1481                 const char *save_as,
1482                 void (*progress_gauge_callback)
1483                         (CtdlIPC*, unsigned long, unsigned long),
1484                 char *cret)
1485 {
1486         int ret;
1487         FILE *uploadFP;
1488         char *aaa;
1489         char MimeTestBuf[64];
1490         const char *MimeType;
1491         long len;
1492
1493         if (!cret) return -1;
1494         if (!save_as) return -1;
1495         if (!path && for_real) return -1;
1496         if (!*path && for_real) return -1;
1497         if (ipc->uploading) return -1;
1498
1499         aaa = (char *)malloc(strlen(save_as) + 17);
1500         if (!aaa) return -1;
1501
1502         uploadFP = fopen(path, "r");
1503         if (!uploadFP) return -2;
1504
1505         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1506         rewind (uploadFP);
1507         if (len < 0) 
1508                 return -3;
1509         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1510
1511         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1512         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1513         free(aaa);
1514         if (ret / 100 == 2 && for_real) {
1515                 ipc->uploading = 1;
1516                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1517                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1518                 ipc->uploading = 0;
1519         }
1520         return ret;
1521 }
1522
1523
1524 /* QUSR */
1525 int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
1526 {
1527         int ret;
1528         char *aaa;
1529
1530         if (!cret) return -2;
1531         if (!username) return -2;
1532
1533         aaa = (char *)malloc(strlen(username) + 6);
1534         if (!aaa) return -1;
1535
1536         sprintf(aaa, "QUSR %s", username);
1537         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1538         free(aaa);
1539         return ret;
1540 }
1541
1542
1543 /* LFLR */
1544 int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
1545 {
1546         size_t bytes;
1547
1548         if (!cret) return -2;
1549         if (!listing) return -2;
1550         if (*listing) return -2;
1551
1552         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1553 }
1554
1555
1556 /* CFLR */
1557 int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
1558 {
1559         int ret;
1560         char aaa[SIZ];
1561
1562         if (!cret) return -2;
1563         if (!name) return -2;
1564
1565         sprintf(aaa, "CFLR %s|%d", name, for_real);
1566         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1567         return ret;
1568 }
1569
1570
1571 /* KFLR */
1572 int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
1573 {
1574         char aaa[SIZ];
1575
1576         if (!cret) return -1;
1577         if (floornum < 0) return -1;
1578
1579         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1580         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1581 }
1582
1583
1584 /* EFLR */
1585 int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
1586 {
1587         int ret;
1588         char aaa[SIZ];
1589
1590         if (!cret) return -2;
1591         if (!floorname) return -2;
1592         if (floornum < 0) return -2;
1593
1594         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1595         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1596         return ret;
1597 }
1598
1599
1600 /*
1601  * IDEN 
1602  *
1603  * You only need to fill out hostname, the defaults will be used if any of the
1604  * other fields are not set properly.
1605  */
1606 int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
1607                 int revision, const char *software_name, const char *hostname,
1608                 char *cret)
1609 {
1610         int ret;
1611         char *aaa;
1612
1613         if (developerid < 0 || clientid < 0 || revision < 0 ||
1614             !software_name) {
1615                 developerid = 8;
1616                 clientid = 0;
1617                 revision = CLIENT_VERSION - 600;
1618                 software_name = "Citadel (libcitadel)";
1619         }
1620         if (!hostname) return -2;
1621
1622         aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
1623         if (!aaa) return -1;
1624
1625         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
1626                         revision, software_name, hostname);
1627         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1628         free(aaa);
1629         return ret;
1630 }
1631
1632
1633 /* SEXP */
1634 int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
1635                 char *cret)
1636 {
1637         int ret;
1638         char *aaa;
1639
1640         if (!cret) return -2;
1641         if (!username) return -2;
1642
1643         aaa = (char *)malloc(strlen(username) + 8);
1644         if (!aaa) return -1;
1645
1646         if (text) {
1647                 sprintf(aaa, "SEXP %s|-", username);
1648                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
1649                                 NULL, NULL, cret);
1650         } else {
1651                 sprintf(aaa, "SEXP %s||", username);
1652                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1653         }
1654         free(aaa);
1655         return ret;
1656 }
1657
1658
1659 /* GEXP */
1660 int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
1661 {
1662         size_t bytes;
1663
1664         if (!cret) return -2;
1665         if (!listing) return -2;
1666         if (*listing) return -2;
1667
1668         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1669 }
1670
1671
1672 /* DEXP */
1673 /* mode is 0 = enable, 1 = disable, 2 = status */
1674 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
1675 {
1676         char aaa[64];
1677
1678         if (!cret) return -2;
1679
1680         sprintf(aaa, "DEXP %d", mode);
1681         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1682 }
1683
1684
1685 /* EBIO */
1686 int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
1687 {
1688         if (!cret) return -2;
1689         if (!bio) return -2;
1690
1691         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
1692                         NULL, NULL, cret);
1693 }
1694
1695
1696 /* RBIO */
1697 int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
1698 {
1699         int ret;
1700         size_t bytes;
1701         char *aaa;
1702
1703         if (!cret) return -2;
1704         if (!username) return -2;
1705         if (!listing) return -2;
1706         if (*listing) return -2;
1707
1708         aaa = (char *)malloc(strlen(username) + 6);
1709         if (!aaa) return -1;
1710
1711         sprintf(aaa, "RBIO %s", username);
1712         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1713         free(aaa);
1714         return ret;
1715 }
1716
1717
1718 /* LBIO */
1719 int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
1720 {
1721         size_t bytes;
1722
1723         if (!cret) return -2;
1724         if (!listing) return -2;
1725         if (*listing) return -2;
1726
1727         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1728 }
1729
1730
1731 /* STEL */
1732 int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
1733 {
1734         char aaa[64];
1735
1736         if (!cret) return -1;
1737
1738         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1739         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1740 }
1741
1742
1743 /* TERM */
1744 int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
1745 {
1746         char aaa[64];
1747
1748         if (!cret) return -1;
1749
1750         sprintf(aaa, "TERM %d", sid);
1751         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1752 }
1753
1754
1755 /* DOWN */
1756 int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
1757 {
1758         if (!cret) return -1;
1759
1760         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1761 }
1762
1763
1764 /* SCDN */
1765 int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
1766 {
1767         char aaa[16];
1768
1769         if (!cret) return -1;
1770
1771         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1772         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1773 }
1774
1775
1776 /* EMSG */
1777 int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
1778                 char *cret)
1779 {
1780         int ret;
1781         char *aaa;
1782
1783         if (!cret) return -2;
1784         if (!text) return -2;
1785         if (!filename) return -2;
1786
1787         aaa = (char *)malloc(strlen(filename) + 6);
1788         if (!aaa) return -1;
1789
1790         sprintf(aaa, "EMSG %s", filename);
1791         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1792         free(aaa);
1793         return ret;
1794 }
1795
1796
1797 /* HCHG */
1798 int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
1799 {
1800         int ret;
1801         char *aaa;
1802
1803         if (!cret) return -2;
1804         if (!hostname) return -2;
1805
1806         aaa = (char *)malloc(strlen(hostname) + 6);
1807         if (!aaa) return -1;
1808
1809         sprintf(aaa, "HCHG %s", hostname);
1810         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1811         free(aaa);
1812         return ret;
1813 }
1814
1815
1816 /* RCHG */
1817 int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
1818 {
1819         int ret;
1820         char *aaa;
1821
1822         if (!cret) return -2;
1823         if (!roomname) return -2;
1824
1825         aaa = (char *)malloc(strlen(roomname) + 6);
1826         if (!aaa) return -1;
1827
1828         sprintf(aaa, "RCHG %s", roomname);
1829         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1830         free(aaa);
1831         return ret;
1832 }
1833
1834
1835 /* UCHG */
1836 int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
1837 {
1838         int ret;
1839         char *aaa;
1840
1841         if (!cret) return -2;
1842         if (!username) return -2;
1843
1844         aaa = (char *)malloc(strlen(username) + 6);
1845         if (!aaa) return -1;
1846
1847         sprintf(aaa, "UCHG %s", username);
1848         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1849         free(aaa);
1850         return ret;
1851 }
1852
1853
1854 /* TIME */
1855 /* This function returns the actual server time reported, or 0 if error */
1856 time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
1857 {
1858         time_t tret;
1859         int ret;
1860
1861         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1862         if (ret / 100 == 2) {
1863                 tret = extract_long(cret, 0);
1864         } else {
1865                 tret = 0L;
1866         }
1867         return tret;
1868 }
1869
1870
1871 /* AGUP */
1872 int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who, struct ctdluser **uret, char *cret)
1873 {
1874         int ret;
1875         char aaa[SIZ];
1876
1877         if (!cret) return -2;
1878         if (!uret) return -2;
1879         if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
1880         if (!*uret) return -1;
1881
1882         sprintf(aaa, "AGUP %s", who);
1883         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1884
1885         if (ret / 100 == 2) {
1886                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1887                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1888                 uret[0]->flags = extract_int(cret, 2);
1889                 uret[0]->timescalled = extract_long(cret, 3);
1890                 uret[0]->posted = extract_long(cret, 4);
1891                 uret[0]->axlevel = extract_int(cret, 5);
1892                 uret[0]->usernum = extract_long(cret, 6);
1893                 uret[0]->lastcall = extract_long(cret, 7);
1894                 uret[0]->USuserpurge = extract_int(cret, 8);
1895         }
1896         return ret;
1897 }
1898
1899
1900 /* ASUP */
1901 int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
1902 {
1903         int ret;
1904         char *aaa;
1905
1906         if (!cret) return -2;
1907         if (!uret) return -2;
1908
1909         aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1910         if (!aaa) return -1;
1911
1912         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1913                 uret->fullname, uret->password, uret->flags, uret->timescalled,
1914                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge
1915         );
1916         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1917         free(aaa);
1918         return ret;
1919 }
1920
1921
1922 /* AGEA */
1923 int CtdlIPCAideGetEmailAddresses(CtdlIPC *ipc, const char *who, char *target_buf, char *cret)
1924 {
1925         int ret;
1926         char aaa[SIZ];
1927         char *emailaddrs = NULL;
1928         size_t emailaddrs_len = 0;
1929
1930         sprintf(aaa, "AGEA %s", who);
1931         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
1932
1933         if (ret / 100 == 1) {
1934                 strcpy(target_buf, emailaddrs);
1935         }
1936
1937         if (emailaddrs != NULL) {
1938                 free(emailaddrs);
1939         }
1940
1941         return ret;
1942 }
1943
1944
1945 /* ASEA */
1946 int CtdlIPCAideSetEmailAddresses(CtdlIPC *ipc, const char *who, char *emailaddrs, char *cret)
1947 {
1948         char aaa[SIZ];
1949         int ret;
1950
1951         if (!who) return -2;
1952         if (!emailaddrs) return -2;
1953         if (!cret) return -2;
1954
1955         sprintf(aaa, "ASEA %s", who);
1956         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
1957         return ret;
1958 }
1959
1960
1961 /* GPEX */
1962 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1963 /* caller must free the struct ExpirePolicy */
1964 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
1965                 struct ExpirePolicy **policy, char *cret)
1966 {
1967         static char *proto[] = {
1968                 strof(roompolicy),
1969                 strof(floorpolicy),
1970                 strof(sitepolicy),
1971                 strof(mailboxespolicy)
1972         };
1973         char cmd[256];
1974         int ret;
1975
1976         if (!cret) return -2;
1977         if (!policy) return -2;
1978         if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
1979         if (!*policy) return -1;
1980         if (which < 0 || which > 3) return -2;
1981         
1982         sprintf(cmd, "GPEX %s", proto[which]);
1983         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
1984         if (ret / 100 == 2) {
1985                 policy[0]->expire_mode = extract_int(cret, 0);
1986                 policy[0]->expire_value = extract_int(cret, 1);
1987         }
1988         return ret;
1989 }
1990
1991
1992 /* SPEX */
1993 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1994 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
1995 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
1996                 struct ExpirePolicy *policy, char *cret)
1997 {
1998         char aaa[38];
1999         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2000
2001         if (!cret) return -2;
2002         if (which < 0 || which > 3) return -2;
2003         if (!policy) return -2;
2004         if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
2005         if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
2006
2007         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
2008                         policy->expire_mode, policy->expire_value);
2009         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2010 }
2011
2012
2013 /* CONF GET */
2014 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
2015 {
2016         size_t bytes;
2017
2018         if (!cret) return -2;
2019         if (!listing) return -2;
2020         if (*listing) return -2;
2021
2022         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
2023                         listing, &bytes, cret);
2024 }
2025
2026
2027 /* CONF SET */
2028 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
2029 {
2030         if (!cret) return -2;
2031         if (!listing) return -2;
2032
2033         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
2034                         NULL, NULL, cret);
2035 }
2036
2037
2038 /* CONF GETSYS */
2039 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, char **listing, char *cret)
2040 {
2041         int ret;
2042         char *aaa;
2043         size_t bytes;
2044
2045         if (!cret) return -2;
2046         if (!mimetype) return -2;
2047         if (!listing) return -2;
2048         if (*listing) return -2;
2049
2050         aaa = malloc(strlen(mimetype) + 13);
2051         if (!aaa) return -1;
2052         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2053         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2054         free(aaa);
2055         return ret;
2056 }
2057
2058
2059 /* CONF PUTSYS */
2060 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, const char *listing, char *cret)
2061 {
2062         int ret;
2063         char *aaa;
2064
2065         if (!cret) return -2;
2066         if (!mimetype) return -2;
2067         if (!listing) return -2;
2068
2069         aaa = malloc(strlen(mimetype) + 13);
2070         if (!aaa) return -1;
2071         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2072         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2073         free(aaa);
2074         return ret;
2075 }
2076
2077
2078 /* GNET */
2079 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
2080 {
2081         size_t bytes;
2082
2083         if (!cret) return -2;
2084         if (!listing) return -2;
2085         if (*listing) return -2;
2086
2087         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2088 }
2089
2090
2091 /* SNET */
2092 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
2093 {
2094         if (!cret) return -2;
2095         if (!listing) return -2;
2096
2097         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2098 }
2099
2100
2101 /* REQT */
2102 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
2103 {
2104         char aaa[64];
2105
2106         if (!cret) return -2;
2107         if (session < 0) return -2;
2108
2109         sprintf(aaa, "REQT %d", session);
2110         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2111 }
2112
2113
2114 /* SEEN */
2115 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
2116 {
2117         char aaa[27];
2118
2119         if (!cret) return -2;
2120         if (msgnum < 0) return -2;
2121
2122         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2123         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2124 }
2125
2126
2127 /* STLS */
2128 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2129 {
2130         int a;
2131         int r;
2132         char buf[SIZ];
2133
2134 #ifdef HAVE_OPENSSL
2135         SSL *temp_ssl;
2136
2137         /* New SSL object */
2138         temp_ssl = SSL_new(ssl_ctx);
2139         if (!temp_ssl) {
2140                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2141                 return -2;
2142         }
2143         /* Pointless flag waving */
2144 #if SSLEAY_VERSION_NUMBER >= 0x0922
2145         SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
2146 #endif
2147
2148         /* Associate network connection with SSL object */
2149         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2150                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2151                 return -2;
2152         }
2153
2154         if (status_hook != NULL) {
2155                 status_hook("Requesting encryption...\r");
2156         }
2157
2158         /* Ready to start SSL/TLS */
2159         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2160         if (r / 100 != 2) {
2161                 error_printf("Server can't start TLS: %s\n", buf);
2162                 endtls(temp_ssl);
2163                 return r;
2164         }
2165
2166         /* Do SSL/TLS handshake */
2167         if ((a = SSL_connect(temp_ssl)) < 1) {
2168                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2169                 endtls(temp_ssl);
2170                 return -2;
2171         }
2172         ipc->ssl = temp_ssl;
2173
2174         error_printf("Encrypting with %s cipher %s\n",
2175                 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2176                 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2177         );
2178         return r;
2179 #else
2180         return 0;
2181 #endif /* HAVE_OPENSSL */
2182 }
2183
2184
2185 #ifdef HAVE_OPENSSL
2186 static void endtls(SSL *ssl)
2187 {
2188         if (ssl) {
2189                 SSL_shutdown(ssl);
2190                 SSL_free(ssl);
2191         }
2192 }
2193 #endif
2194
2195
2196 /* QDIR */
2197 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2198 {
2199         int ret;
2200         char *aaa;
2201
2202         if (!address) return -2;
2203         if (!cret) return -2;
2204
2205         aaa = (char *)malloc(strlen(address) + 6);
2206         if (!aaa) return -1;
2207
2208         sprintf(aaa, "QDIR %s", address);
2209         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2210         free(aaa);
2211         return ret;
2212 }
2213
2214
2215 /* IPGM */
2216 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2217 {
2218         char aaa[30];
2219
2220         if (!cret) return -2;
2221         sprintf(aaa, "IPGM %d", secret);
2222         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2223 }
2224
2225
2226
2227
2228 /* ************************************************************************** */
2229 /*           Stuff below this line is not for public consumption            */
2230 /* ************************************************************************** */
2231
2232
2233 /* Read a listing from the server up to 000.  Append to dest if it exists */
2234 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2235 {
2236         size_t length = 0;
2237         size_t linelength;
2238         char *ret = NULL;
2239         char aaa[SIZ];
2240
2241         ret = dest;
2242         if (ret != NULL) {
2243                 length = strlen(ret);
2244         } else {
2245                 length = 0;
2246         }
2247
2248         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2249                 linelength = strlen(aaa);
2250                 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2251                 if (ret) {
2252                         strcpy(&ret[length], aaa);
2253                         length += linelength;
2254                         strcpy(&ret[length++], "\n");
2255                 }
2256         }
2257
2258         return(ret);
2259 }
2260
2261
2262 /* Send a listing to the server; generate the ending 000. */
2263 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2264 {
2265         char *text;
2266
2267         text = (char *)malloc(strlen(listing) + 6);
2268         if (text) {
2269                 strcpy(text, listing);
2270                 while (text[strlen(text) - 1] == '\n')
2271                         text[strlen(text) - 1] = '\0';
2272                 strcat(text, "\n000");
2273                 CtdlIPC_putline(ipc, text);
2274                 free(text);
2275                 text = NULL;
2276         } else {
2277                 /* Malloc failed but we are committed to send */
2278                 /* This may result in extra blanks at the bottom */
2279                 CtdlIPC_putline(ipc, text);
2280                 CtdlIPC_putline(ipc, "000");
2281         }
2282         return 0;
2283 }
2284
2285
2286 /* Partial read of file from server */
2287 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2288 {
2289         size_t len = 0;
2290         char aaa[SIZ];
2291
2292         if (!buf) return 0;
2293         if (!cret) return 0;
2294         if (bytes < 1) return 0;
2295
2296         CtdlIPC_lock(ipc);
2297         sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2298         CtdlIPC_putline(ipc, aaa);
2299         CtdlIPC_getline(ipc, aaa);
2300         if (aaa[0] != '6')
2301                 strcpy(cret, &aaa[4]);
2302         else {
2303                 len = extract_long(&aaa[4], 0);
2304                 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2305                 if (*buf) {
2306                         /* I know what I'm doing */
2307                         serv_read(ipc, ((char *)(*buf) + offset), len);
2308                 } else {
2309                         /* We have to read regardless */
2310                         serv_read(ipc, aaa, len);
2311                         len = 0;
2312                 }
2313         }
2314         CtdlIPC_unlock(ipc);
2315         return len;
2316 }
2317
2318
2319 /* CLOS */
2320 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2321 {
2322         int ret;
2323
2324         if (!cret) return -2;
2325         if (!ipc->downloading) return -2;
2326
2327         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2328         if (ret / 100 == 2)
2329                 ipc->downloading = 0;
2330         return ret;
2331 }
2332
2333
2334 /* MSGP */
2335 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2336         int ret;
2337         char cmd[SIZ];
2338         
2339         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2340         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2341         return ret;
2342 }
2343
2344
2345
2346 /* READ */
2347 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2348                 void (*progress_gauge_callback)
2349                         (CtdlIPC*, unsigned long, unsigned long),
2350                char *cret)
2351 {
2352         size_t len;
2353
2354         if (!cret) return -1;
2355         if (!buf) return -1;
2356         if (*buf) return -1;
2357         if (!ipc->downloading) return -1;
2358
2359         len = resume;
2360         if (progress_gauge_callback)
2361                 progress_gauge_callback(ipc, len, bytes);
2362         while (len < bytes) {
2363                 size_t block;
2364
2365                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2366                 if (block == 0) {
2367                         free(*buf);
2368                         return 0;
2369                 }
2370                 len += block;
2371                 if (progress_gauge_callback)
2372                         progress_gauge_callback(ipc, len, bytes);
2373         }
2374         return len;
2375 }
2376
2377 /* READ - pipelined */
2378 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2379                size_t resume,
2380                 void (*progress_gauge_callback)
2381                         (CtdlIPC*, unsigned long, unsigned long),
2382                char *cret)
2383 {
2384         size_t len;
2385         int calls;      /* How many calls in the pipeline */
2386         int i;          /* iterator */
2387         char aaa[4096];
2388
2389         if (!cret) return -1;
2390         if (!buf) return -1;
2391         if (*buf) return -1;
2392         if (!ipc->downloading) return -1;
2393
2394         *buf = (void *)realloc(*buf, bytes - resume);
2395         if (!*buf) return -1;
2396
2397         len = 0;
2398         CtdlIPC_lock(ipc);
2399         if (progress_gauge_callback)
2400                 progress_gauge_callback(ipc, len, bytes);
2401
2402         /* How many calls will be in the pipeline? */
2403         calls = (bytes - resume) / 4096;
2404         if ((bytes - resume) % 4096) calls++;
2405
2406         /* Send all requests at once */
2407         for (i = 0; i < calls; i++) {
2408                 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2409                 CtdlIPC_putline(ipc, aaa);
2410         }
2411
2412         /* Receive all responses at once */
2413         for (i = 0; i < calls; i++) {
2414                 CtdlIPC_getline(ipc, aaa);
2415                 if (aaa[0] != '6')
2416                         strcpy(cret, &aaa[4]);
2417                 else {
2418                         len = extract_long(&aaa[4], 0);
2419                         /* I know what I'm doing */
2420                         serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2421                 }
2422                 if (progress_gauge_callback)
2423                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2424         }
2425         CtdlIPC_unlock(ipc);
2426         return len;
2427 }
2428
2429
2430 /* UCLS */
2431 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2432 {
2433         int ret;
2434         char cmd[8];
2435
2436         if (!cret) return -1;
2437         if (!ipc->uploading) return -1;
2438
2439         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2440         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2441         ipc->uploading = 0;
2442         return ret;
2443 }
2444
2445
2446 /* WRIT */
2447 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2448                 void (*progress_gauge_callback)
2449                         (CtdlIPC*, unsigned long, unsigned long),
2450                 char *cret)
2451 {
2452         int ret = -1;
2453         size_t offset = 0;
2454         size_t bytes;
2455         char aaa[SIZ];
2456         char buf[4096];
2457         FILE *fd = uploadFP;
2458         int ferr;
2459
2460         if (!cret) return -1;
2461
2462         fseek(fd, 0L, SEEK_END);
2463         bytes = ftell(fd);
2464         rewind(fd);
2465
2466         if (progress_gauge_callback)
2467                 progress_gauge_callback(ipc, 0, bytes);
2468
2469         while (offset < bytes) {
2470                 size_t to_write;
2471
2472                 /* Read some data in */
2473                 to_write = fread(buf, 1, 4096, fd);
2474                 if (!to_write) {
2475                         if (feof(fd) || ferror(fd)) break;
2476                 }
2477                 sprintf(aaa, "WRIT %d", (int)to_write);
2478                 CtdlIPC_putline(ipc, aaa);
2479                 CtdlIPC_getline(ipc, aaa);
2480                 strcpy(cret, &aaa[4]);
2481                 ret = atoi(aaa);
2482                 if (aaa[0] == '7') {
2483                         to_write = extract_long(&aaa[4], 0);
2484                         
2485                         serv_write(ipc, buf, to_write);
2486                         offset += to_write;
2487                         if (progress_gauge_callback)
2488                                 progress_gauge_callback(ipc, offset, bytes);
2489                         /* Detect short reads and back up if needed */
2490                         /* offset will never be negative anyway */
2491                         fseek(fd, (signed)offset, SEEK_SET);
2492                 } else {
2493                         break;
2494                 }
2495         }
2496         if (progress_gauge_callback)
2497                 progress_gauge_callback(ipc, 1, 1);
2498         ferr = ferror(fd);
2499         fclose(fd);
2500         return (!ferr ? ret : -2);
2501 }
2502
2503
2504 /*
2505  * Generic command method.  This method should handle any server command
2506  * except for CHAT.  It takes the following arguments:
2507  *
2508  * ipc                  The server to speak with
2509  * command              Preformatted command to send to server
2510  * to_send              A text or binary file to send to server
2511  *                      (only sent if server requests it)
2512  * bytes_to_send        The number of bytes in to_send (required if
2513  *                      sending binary, optional if sending listing)
2514  * to_receive           Pointer to a NULL pointer, if the server
2515  *                      sends text or binary we will allocate memory
2516  *                      for the file and stuff it here
2517  * bytes_to_receive     If a file is received, we will store its
2518  *                      byte count here
2519  * proto_response       The protocol response.  Caller must provide
2520  *                      this buffer and ensure that it is at least
2521  *                      128 bytes in length.
2522  *
2523  * This function returns a number equal to the protocol response number,
2524  * -1 if an internal error occurred, -2 if caller provided bad values,
2525  * or 0 - the protocol response number if bad values were found during
2526  * the protocol exchange.
2527  * It stores the protocol response string (minus the number) in 
2528  * protocol_response as described above.  Some commands send additional
2529  * data in this string.
2530  */
2531 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2532                 const char *command, const char *to_send,
2533                 size_t bytes_to_send, char **to_receive, 
2534                 size_t *bytes_to_receive, char *proto_response)
2535 {
2536         char buf[SIZ];
2537         int ret;
2538
2539         if (!command) return -2;
2540         if (!proto_response) return -2;
2541
2542         CtdlIPC_lock(ipc);
2543         CtdlIPC_putline(ipc, command);
2544         while (1) {
2545                 CtdlIPC_getline(ipc, proto_response);
2546                 if (proto_response[3] == '*')
2547                         instant_msgs = 1;
2548                 ret = atoi(proto_response);
2549                 strcpy(proto_response, &proto_response[4]);
2550                 switch (ret / 100) {
2551                 default:                        /* Unknown, punt */
2552                 case 2:                         /* OK */
2553                 case 3:                         /* MORE_DATA */
2554                 case 5:                         /* ERROR */
2555                         /* Don't need to do anything */
2556                         break;
2557                 case 1:                         /* LISTING_FOLLOWS */
2558                         if (to_receive && !*to_receive && bytes_to_receive) {
2559                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2560                         } else { /* Drain */
2561                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2562                                 ret = -ret;
2563                         }
2564                         break;
2565                 case 4:                         /* SEND_LISTING */
2566                         if (to_send) {
2567                                 CtdlIPCSendListing(ipc, to_send);
2568                         } else {
2569                                 /* No listing given, fake it */
2570                                 CtdlIPC_putline(ipc, "000");
2571                                 ret = -ret;
2572                         }
2573                         break;
2574                 case 6:                         /* BINARY_FOLLOWS */
2575                         if (to_receive && !*to_receive && bytes_to_receive) {
2576                                 *bytes_to_receive =
2577                                         extract_long(proto_response, 0);
2578                                 *to_receive = (char *)
2579                                         malloc((size_t)*bytes_to_receive);
2580                                 if (!*to_receive) {
2581                                         ret = -1;
2582                                 } else {
2583                                         serv_read(ipc, *to_receive,
2584                                                         *bytes_to_receive);
2585                                 }
2586                         } else {
2587                                 /* Drain */
2588                                 size_t drain;
2589
2590                                 drain = extract_long(proto_response, 0);
2591                                 while (drain > SIZ) {
2592                                         serv_read(ipc, buf, SIZ);
2593                                         drain -= SIZ;
2594                                 }
2595                                 serv_read(ipc, buf, drain);
2596                                 ret = -ret;
2597                         }
2598                         break;
2599                 case 7:                         /* SEND_BINARY */
2600                         if (to_send && bytes_to_send) {
2601                                 serv_write(ipc, to_send, bytes_to_send);
2602                         } else if (bytes_to_send) {
2603                                 /* Fake it, send nulls */
2604                                 size_t fake;
2605
2606                                 fake = bytes_to_send;
2607                                 memset(buf, '\0', SIZ);
2608                                 while (fake > SIZ) {
2609                                         serv_write(ipc, buf, SIZ);
2610                                         fake -= SIZ;
2611                                 }
2612                                 serv_write(ipc, buf, fake);
2613                                 ret = -ret;
2614                         } /* else who knows?  DANGER WILL ROBINSON */
2615                         break;
2616                 case 8:                         /* START_CHAT_MODE */
2617                         if (!strncasecmp(command, "CHAT", 4)) {
2618                                 /* Don't call chatmode with generic! */
2619                                 CtdlIPC_putline(ipc, "/quit");
2620                                 ret = -ret;
2621                         } else {
2622                                 /* In this mode we send then receive listing */
2623                                 if (to_send) {
2624                                         CtdlIPCSendListing(ipc, to_send);
2625                                 } else {
2626                                         /* No listing given, fake it */
2627                                         CtdlIPC_putline(ipc, "000");
2628                                         ret = -ret;
2629                                 }
2630                                 if (to_receive && !*to_receive
2631                                                 && bytes_to_receive) {
2632                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2633                                 } else { /* Drain */
2634                                         while (CtdlIPC_getline(ipc, buf),
2635                                                         strcmp(buf, "000")) ;
2636                                         ret = -ret;
2637                                 }
2638                         }
2639                         break;
2640                 case 9:                         /* ASYNC_MSG */
2641                         /* CtdlIPCDoAsync(ret, proto_response); */
2642                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2643                         break;
2644                 }
2645                 if (ret / 100 != 9)
2646                         break;
2647         }
2648         CtdlIPC_unlock(ipc);
2649         return ret;
2650 }
2651
2652
2653 /*
2654  * Connect to a Citadel on a remote host using a TCP/IP socket
2655  */
2656 static int tcp_connectsock(char *host, char *service)
2657 {
2658         struct in6_addr serveraddr;
2659         struct addrinfo hints;
2660         struct addrinfo *res = NULL;
2661         struct addrinfo *ai = NULL;
2662         int rc = (-1);
2663         int sock = (-1);
2664
2665         if ((host == NULL) || IsEmptyStr(host)) {
2666                 service = DEFAULT_HOST ;
2667         }
2668         if ((service == NULL) || IsEmptyStr(service)) {
2669                 service = DEFAULT_PORT ;
2670         }
2671
2672         memset(&hints, 0x00, sizeof(hints));
2673         hints.ai_flags = AI_NUMERICSERV;
2674         hints.ai_family = AF_UNSPEC;
2675         hints.ai_socktype = SOCK_STREAM;
2676
2677         /*
2678          * Handle numeric IPv4 and IPv6 addresses
2679          */
2680         rc = inet_pton(AF_INET, host, &serveraddr);
2681         if (rc == 1) {                                          /* dotted quad */
2682                 hints.ai_family = AF_INET;
2683                 hints.ai_flags |= AI_NUMERICHOST;
2684         }
2685         else {
2686                 rc = inet_pton(AF_INET6, host, &serveraddr);
2687                 if (rc == 1) {                                  /* IPv6 address */
2688                         hints.ai_family = AF_INET6;
2689                         hints.ai_flags |= AI_NUMERICHOST;
2690                 }
2691         }
2692
2693         /* Begin the connection process */
2694
2695         rc = getaddrinfo(host, service, &hints, &res);
2696         if (rc != 0) {
2697                 return(-1);
2698         }
2699
2700         /*
2701          * Try all available addresses until we connect to one or until we run out.
2702          */
2703         for (ai = res; ai != NULL; ai = ai->ai_next) {
2704                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2705                 if (sock < 0) return(-1);
2706
2707                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2708                 if (rc >= 0) {
2709                         return(sock);           /* Connected! */
2710                 }
2711                 else {
2712                         close(sock);            /* Failed.  Close the socket to avoid fd leak! */
2713                 }
2714         }
2715
2716         return(-1);
2717 }
2718
2719
2720
2721
2722
2723 /*
2724  * Connect to a Citadel on the local host using a unix domain socket
2725  */
2726 static int uds_connectsock(int *isLocal, char *sockpath)
2727 {
2728         struct sockaddr_un addr;
2729         int s;
2730
2731         memset(&addr, 0, sizeof(addr));
2732         addr.sun_family = AF_UNIX;
2733         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2734
2735         s = socket(AF_UNIX, SOCK_STREAM, 0);
2736         if (s < 0) {
2737                 return -1;
2738         }
2739
2740         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2741                 close(s);
2742                 return -1;
2743         }
2744
2745         *isLocal = 1;
2746         return s;
2747 }
2748
2749
2750 /*
2751  * input binary data from socket
2752  */
2753 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2754 {
2755         unsigned int len, rlen;
2756
2757 #if defined(HAVE_OPENSSL)
2758         if (ipc->ssl) {
2759                 serv_read_ssl(ipc, buf, bytes);
2760                 return;
2761         }
2762 #endif
2763         len = 0;
2764         while (len < bytes) {
2765                 rlen = read(ipc->sock, &buf[len], bytes - len);
2766                 if (rlen < 1) {
2767                         connection_died(ipc, 0);
2768                         return;
2769                 }
2770                 len += rlen;
2771         }
2772 }
2773
2774
2775 /*
2776  * send binary to server
2777  */
2778 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2779 {
2780         unsigned int bytes_written = 0;
2781         int retval;
2782
2783 #if defined(HAVE_OPENSSL)
2784         if (ipc->ssl) {
2785                 serv_write_ssl(ipc, buf, nbytes);
2786                 return;
2787         }
2788 #endif
2789         while (bytes_written < nbytes) {
2790                 retval = write(ipc->sock, &buf[bytes_written],
2791                                nbytes - bytes_written);
2792                 if (retval < 1) {
2793                         connection_died(ipc, 0);
2794                         return;
2795                 }
2796                 bytes_written += retval;
2797         }
2798 }
2799
2800
2801 #ifdef HAVE_OPENSSL
2802 /*
2803  * input binary data from encrypted connection
2804  */
2805 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2806 {
2807         int len, rlen;
2808         char junk[1];
2809
2810         len = 0;
2811         while (len < bytes) {
2812                 if (SSL_want_read(ipc->ssl)) {
2813                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2814                                 error_printf("SSL_write in serv_read:\n");
2815                                 ERR_print_errors_fp(stderr);
2816                         }
2817                 }
2818                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2819                 if (rlen < 1) {
2820                         long errval;
2821
2822                         errval = SSL_get_error(ipc->ssl, rlen);
2823                         if (errval == SSL_ERROR_WANT_READ ||
2824                                         errval == SSL_ERROR_WANT_WRITE) {
2825                                 sleep(1);
2826                                 continue;
2827                         }
2828 /***
2829  Not sure why we'd want to handle these error codes any differently,
2830  but this definitely isn't the way to handle them.  Someone must have
2831  naively assumed that we could fall back to unencrypted communications,
2832  but all it does is just recursively blow the stack.
2833                         if (errval == SSL_ERROR_ZERO_RETURN ||
2834                                         errval == SSL_ERROR_SSL) {
2835                                 serv_read(ipc, &buf[len], bytes - len);
2836                                 return;
2837                         }
2838  ***/
2839                         error_printf("SSL_read in serv_read: %s\n",
2840                                         ERR_reason_error_string(ERR_peek_error()));
2841                         connection_died(ipc, 1);
2842                         return;
2843                 }
2844                 len += rlen;
2845         }
2846 }
2847
2848
2849 /*
2850  * send binary to server encrypted
2851  */
2852 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2853 {
2854         unsigned int bytes_written = 0;
2855         int retval;
2856         char junk[1];
2857
2858         while (bytes_written < nbytes) {
2859                 if (SSL_want_write(ipc->ssl)) {
2860                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2861                                 error_printf("SSL_read in serv_write:\n");
2862                                 ERR_print_errors_fp(stderr);
2863                         }
2864                 }
2865                 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2866                                 nbytes - bytes_written);
2867                 if (retval < 1) {
2868                         long errval;
2869
2870                         errval = SSL_get_error(ipc->ssl, retval);
2871                         if (errval == SSL_ERROR_WANT_READ ||
2872                                         errval == SSL_ERROR_WANT_WRITE) {
2873                                 sleep(1);
2874                                 continue;
2875                         }
2876                         if (errval == SSL_ERROR_ZERO_RETURN ||
2877                                         errval == SSL_ERROR_SSL) {
2878                                 serv_write(ipc, &buf[bytes_written],
2879                                                 nbytes - bytes_written);
2880                                 return;
2881                         }
2882                         error_printf("SSL_write in serv_write: %s\n",
2883                                         ERR_reason_error_string(ERR_peek_error()));
2884                         connection_died(ipc, 1);
2885                         return;
2886                 }
2887                 bytes_written += retval;
2888         }
2889 }
2890
2891
2892
2893
2894 static void CtdlIPC_init_OpenSSL(void)
2895 {
2896         int a;
2897         const SSL_METHOD *ssl_method;
2898         DH *dh;
2899         
2900         /* already done init */
2901         if (ssl_ctx) {
2902                 return;
2903         }
2904
2905         /* Get started */
2906         a = 0;
2907         ssl_ctx = NULL;
2908         dh = NULL;
2909         SSL_load_error_strings();
2910         SSLeay_add_ssl_algorithms();
2911
2912         /* Set up the SSL context in which we will oeprate */
2913         ssl_method = SSLv23_client_method();
2914         ssl_ctx = SSL_CTX_new(ssl_method);
2915         if (!ssl_ctx) {
2916                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2917                 return;
2918         }
2919         /* Any reasonable cipher we can get */
2920         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2921                 error_printf("No ciphers available for encryption\n");
2922                 return;
2923         }
2924         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2925
2926         /* Load DH parameters into the context */
2927         dh = DH_new();
2928         if (!dh) {
2929                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
2930                 return;
2931         }
2932
2933         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
2934                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
2935                 DH_free(dh);
2936                 return;
2937         }
2938
2939         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
2940         DH_free(dh);
2941 }
2942
2943 #endif /* HAVE_OPENSSL */
2944
2945
2946 int
2947 ReadNetworkChunk(CtdlIPC* ipc)
2948 {
2949         fd_set read_fd;
2950         int ret = 0;
2951         int err = 0;
2952         struct timeval tv;
2953         size_t n;
2954
2955         tv.tv_sec = 1;
2956         tv.tv_usec = 1000;
2957         /*tries = 0; */
2958         n = 0;
2959         while (1)
2960         {
2961                 errno=0;
2962                 FD_ZERO(&read_fd);
2963                 FD_SET(ipc->sock, &read_fd);
2964                 ret = select(ipc->sock+1, &read_fd, NULL, NULL,  &tv);
2965                 
2966                 if (ret > 0) {
2967                         
2968                         *(ipc->BufPtr) = '\0';
2969                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1, 0);
2970                         if (n > 0) {
2971                                 ipc->BufPtr[n]='\0';
2972                                 ipc->BufUsed += n;
2973                                 return n;
2974                         }
2975                         else 
2976                                 return n;
2977                 }
2978                 else if (ret < 0) {
2979                         if (!(errno == EINTR || errno == EAGAIN))
2980                                 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
2981                         return -1;
2982                 }/*
2983                 else {
2984                         tries ++;
2985                         if (tries >= 10)
2986                         n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
2987                         if (n > 0) {
2988                                 ipc->BufPtr[n]='\0';
2989                                 ipc->BufUsed += n;
2990                                 return n;
2991                         }
2992                         else {
2993                                 connection_died(ipc, 0);
2994                                 return -1;
2995                         }
2996                         }*/
2997         }
2998 }
2999
3000 /*
3001  * input string from socket - implemented in terms of serv_read()
3002  */
3003 #ifdef CHUNKED_READ
3004
3005 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3006 {
3007         int i, ntries;
3008         char *aptr, *bptr, *aeptr, *beptr;
3009
3010 //      error_printf("---\n");
3011
3012         beptr = buf + SIZ;
3013 #if defined(HAVE_OPENSSL)
3014         if (ipc->ssl) {
3015                 
3016                 /* Read one character at a time. */
3017                 for (i = 0;; i++) {
3018                         serv_read(ipc, &buf[i], 1);
3019                         if (buf[i] == '\n' || i == (SIZ-1))
3020                                 break;
3021                 }
3022                 
3023                 /* If we got a long line, discard characters until the newline. */
3024                 if (i == (SIZ-1))
3025                         while (buf[i] != '\n')
3026                                 serv_read(ipc, &buf[i], 1);
3027                 
3028                 /* Strip the trailing newline (and carriage return, if present) */
3029                 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3030                 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3031         }
3032         else
3033 #endif
3034         {
3035                 if (ipc->Buf == NULL)
3036                 {
3037                         ipc->BufSize = SIZ;
3038                         ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3039                         *(ipc->Buf) = '\0';
3040                         ipc->BufPtr = ipc->Buf;
3041                 }
3042
3043                 ntries = 0;
3044 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3045                 if (ipc->BufUsed == 0)
3046                         ReadNetworkChunk(ipc);
3047
3048 ////            if (ipc->BufUsed != 0) while (1)
3049                 bptr = buf;
3050
3051                 while (1)
3052                 {
3053                         aptr = ipc->BufPtr;
3054                         aeptr = ipc->Buf + ipc->BufSize;
3055                         while ((aptr < aeptr) && 
3056                                (bptr < beptr) &&
3057                                (*aptr != '\0') && 
3058                                (*aptr != '\n'))
3059                                 *(bptr++) = *(aptr++);
3060                         if ((*aptr == '\n') && (aptr < aeptr))
3061                         {
3062                                 /* Terminate it right, remove the line breaks */
3063                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3064                                         aptr ++;
3065                                 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3066                                         aptr ++;
3067                                 *(bptr++) = '\0';
3068 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3069                                 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3070                                         *(--bptr) = '\0';
3071                                 
3072                                 /* is there more in the buffer we need to read later? */
3073                                 if (ipc->Buf + ipc->BufUsed > aptr)
3074                                 {
3075                                         ipc->BufPtr = aptr;
3076                                 }
3077                                 else
3078                                 {
3079                                         ipc->BufUsed = 0;
3080                                         ipc->BufPtr = ipc->Buf;
3081                                 }
3082 //                              error_printf("----bla6\n");
3083                                 return;
3084                                 
3085                         }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3086                         else if ((ipc->BufPtr != ipc->Buf) && 
3087                                  (ipc->BufUsed > (ipc->BufSize  - (ipc->BufSize / 4))))
3088                         {
3089                                 size_t NewBufSize = ipc->BufSize * 2;
3090                                 int delta = (ipc->BufPtr - ipc->Buf);
3091                                 char *NewBuf;
3092
3093                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3094                                 NewBuf = (char *)malloc (NewBufSize + 10);
3095                                 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3096                                 free(ipc->Buf);
3097                                 ipc->Buf = ipc->BufPtr = NewBuf;
3098                                 ipc->BufUsed -= delta;
3099                                 ipc->BufSize = NewBufSize;
3100                         }
3101                         if (ReadNetworkChunk(ipc) <0)
3102                         {
3103 //                              error_printf("----bla\n");
3104                                 return;
3105                         }
3106                 }
3107 ///             error_printf("----bl45761%s\nipc->BufUsed");
3108         }
3109 //      error_printf("----bla1\n");
3110 }
3111
3112 #else   /* CHUNKED_READ */
3113
3114 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3115 {
3116         int i;
3117
3118         /* Read one character at a time. */
3119         for (i = 0;; i++) {
3120                 serv_read(ipc, &buf[i], 1);
3121                 if (buf[i] == '\n' || i == (SIZ-1))
3122                         break;
3123         }
3124
3125         /* If we got a long line, discard characters until the newline. */
3126         if (i == (SIZ-1))
3127                 while (buf[i] != '\n')
3128                         serv_read(ipc, &buf[i], 1);
3129
3130         /* Strip the trailing newline (and carriage return, if present) */
3131         if (i>=0 && buf[i] == 10) buf[i--] = 0;
3132         if (i>=0 && buf[i] == 13) buf[i--] = 0;
3133 }
3134
3135
3136 #endif  /* CHUNKED_READ */
3137
3138
3139 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3140 {
3141         CtdlIPC_getline(ipc, buf);
3142 }
3143
3144 /*
3145  * send line to server - implemented in terms of serv_write()
3146  */
3147 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3148 {
3149         char *cmd = NULL;
3150         int len;
3151
3152         len = strlen(buf);
3153         cmd = malloc(len + 2);
3154         if (!cmd) {
3155                 /* This requires no extra memory */
3156                 serv_write(ipc, buf, len);
3157                 serv_write(ipc, "\n", 1);
3158         } else {
3159                 /* This is network-optimized */
3160                 strncpy(cmd, buf, len);
3161                 strcpy(cmd + len, "\n");
3162                 serv_write(ipc, cmd, len + 1);
3163                 free(cmd);
3164         }
3165
3166         ipc->last_command_sent = time(NULL);
3167 }
3168
3169 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3170 {
3171         CtdlIPC_putline(ipc, buf);
3172 }
3173
3174
3175 /*
3176  * attach to server
3177  */
3178 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3179 {
3180         int a;
3181         char cithost[SIZ];
3182         char citport[SIZ];
3183         char sockpath[SIZ];
3184         CtdlIPC* ipc;
3185
3186         ipc = malloc(sizeof(struct _CtdlIPC));
3187         if (!ipc) {
3188                 return 0;
3189         }
3190 #if defined(HAVE_OPENSSL)
3191         ipc->ssl = NULL;
3192         CtdlIPC_init_OpenSSL();
3193 #endif
3194         ipc->sock = -1;                 /* Not connected */
3195         ipc->isLocal = 0;               /* Not local, of course! */
3196         ipc->downloading = 0;
3197         ipc->uploading = 0;
3198         ipc->last_command_sent = 0L;
3199         ipc->network_status_cb = NULL;
3200         ipc->Buf = NULL;
3201         ipc->BufUsed = 0;
3202         ipc->BufPtr = NULL;
3203
3204         strcpy(cithost, DEFAULT_HOST);  /* default host */
3205         strcpy(citport, DEFAULT_PORT);  /* default port */
3206
3207         /* Allow caller to supply our values */
3208         if (hostbuf && strlen(hostbuf) > 0) {
3209                 strcpy(cithost, hostbuf);
3210         }
3211         if (portbuf && strlen(portbuf) > 0) {
3212                 strcpy(citport, portbuf);
3213         }
3214
3215         /* Read host/port from command line if present */
3216         for (a = 0; a < argc; ++a) {
3217                 if (a == 0) {
3218                         /* do nothing */
3219                 } else if (a == 1) {
3220                         strcpy(cithost, argv[a]);
3221                 } else if (a == 2) {
3222                         strcpy(citport, argv[a]);
3223                 } else {
3224                         error_printf("%s: usage: ",argv[0]);
3225                         error_printf("%s [host] [port] ",argv[0]);
3226                         free(ipc);
3227                         errno = EINVAL;
3228                         return 0;
3229                 }
3230         }
3231
3232         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3233                 ipc->isLocal = 1;
3234         }
3235
3236         /* If we're using a unix domain socket we can do a bunch of stuff */
3237         if (!strcmp(cithost, UDS)) {
3238                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3239                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3240                 }
3241                 else {
3242                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3243                 }
3244                 printf("[%s]\n", sockpath);
3245                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3246                 if (ipc->sock == -1) {
3247                         free(ipc);
3248                         return 0;
3249                 }
3250                 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3251                 if (portbuf != NULL) strcpy(portbuf, sockpath);
3252                 strcpy(ipc->ip_hostname, "");
3253                 strcpy(ipc->ip_address, "");
3254                 return ipc;
3255         }
3256
3257         printf("[%s:%s]\n", cithost, citport);
3258         ipc->sock = tcp_connectsock(cithost, citport);
3259         if (ipc->sock == -1) {
3260                 free(ipc);
3261                 return 0;
3262         }
3263
3264
3265         /* Learn the actual network identity of the host to which we are connected */
3266
3267         struct sockaddr_in6 clientaddr;
3268         unsigned int addrlen = sizeof(clientaddr);
3269
3270         ipc->ip_hostname[0] = 0;
3271         ipc->ip_address[0] = 0;
3272
3273         getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
3274         getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3275                 ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
3276         );
3277         getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3278                 ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
3279         );
3280
3281         /* stuff other things elsewhere */
3282
3283         if (hostbuf != NULL) strcpy(hostbuf, cithost);
3284         if (portbuf != NULL) strcpy(portbuf, citport);
3285         return ipc;
3286 }
3287
3288
3289 /*
3290  * Disconnect and delete the IPC class (destructor)
3291  */
3292 void CtdlIPC_delete(CtdlIPC* ipc)
3293 {
3294 #ifdef HAVE_OPENSSL
3295         if (ipc->ssl) {
3296                 SSL_shutdown(ipc->ssl);
3297                 SSL_free(ipc->ssl);
3298                 ipc->ssl = NULL;
3299         }
3300 #endif
3301         if (ipc->sock > -1) {
3302                 shutdown(ipc->sock, 2); /* Close it up */
3303                 ipc->sock = -1;
3304         }
3305         if (ipc->Buf != NULL)
3306                 free (ipc->Buf);
3307         ipc->Buf = NULL;
3308         ipc->BufPtr = NULL;
3309         free(ipc);
3310 }
3311
3312
3313 /*
3314  * Disconnect and delete the IPC class (destructor)
3315  * Also NULLs out the pointer
3316  */
3317 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3318 {
3319         CtdlIPC_delete(*pipc);
3320         *pipc = NULL;
3321 }
3322
3323
3324 /*
3325  * return the file descriptor of the server socket so we can select() on it.
3326  *
3327  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3328  * rewritten...
3329  */
3330 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3331 {
3332         return ipc->sock;
3333 }
3334
3335
3336 /*
3337  * return one character
3338  *
3339  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3340  * rewritten...
3341  */
3342 char CtdlIPC_get(CtdlIPC* ipc)
3343 {
3344         char buf[2];
3345         char ch;
3346
3347         serv_read(ipc, buf, 1);
3348         ch = (int) buf[0];
3349
3350         return (ch);
3351 }