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